pax_global_header00006660000000000000000000000064122203223200014476gustar00rootroot0000000000000052 comment=5e59cd0a7125fb4a378b6ef98a12789d3ec592cc fio-2.1.3/000077500000000000000000000000001222032232000122565ustar00rootroot00000000000000fio-2.1.3/.gitignore000066400000000000000000000001401222032232000142410ustar00rootroot00000000000000*.d *.o /.depend /FIO-VERSION-FILE /config-host.h /config-host.mak /config.log /cscope.out /fio fio-2.1.3/COPYING000066400000000000000000000431311222032232000133130ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. fio-2.1.3/FIO-VERSION-GEN000077500000000000000000000013231222032232000143520ustar00rootroot00000000000000#!/bin/sh GVF=FIO-VERSION-FILE DEF_VER=fio-2.1.3 LF=' ' # First see if there is a version file (included in release tarballs), # then try git-describe, then default. if test -f version then VN=`cat version` || VN="$DEF_VER" elif test -d .git -o -f .git && VN=`git describe --match "fio-[0-9]*" --abbrev=4 HEAD 2>/dev/null` && case "$VN" in *$LF*) (exit 1) ;; v[0-9]*) git update-index -q --refresh test -z "`git diff-index --name-only HEAD --`" || VN="$VN-dirty" ;; esac then VN=$VN else VN="$DEF_VER" fi VN=`expr "$VN" : v*'\(.*\)'` if test -r $GVF then VC=`sed -e 's/^FIO_VERSION = //' <$GVF` else VC=unset fi test "$VN" = "$VC" || { echo >&2 "FIO_VERSION = $VN" echo "FIO_VERSION = $VN" >$GVF } fio-2.1.3/GFIO-TODO000066400000000000000000000036071222032232000134760ustar00rootroot00000000000000In no particular order: - Ability to save job files. Probably in an extended gfio format, so we can include options/settings outside of a fio job file. - End view improvements: - Cleanup the layout - Add ability to save the results - Add ability to load end-results as well - Add ability to request graphs of whatever graphing options the fio job included. - Add ability to graph completion latencies, percentiles, etc. - Add ability to edit job options: - We need an options view after sending a job, that allows us to visually see what was parsed, make changes, resubmit. - Job options are already converted across the network and are available in gfio_client->o for view/edit. We'll need a FIO_NET_CMD_UPDATE_OPTIONS command to send them back, and backend support for updating an existing set of options. - Add support for printing end results, graphs, etc. - Improve the auto-start backend functionality, it's quite buggy. - Ensure that it works on OSX and Windows. We'll need a bit of porting work there. - Persistent store of prefences set. This will need a per-OS bit as well, using gfonf on Linux, registry on Windows, ?? on OSX. - Ensure that local errors go to our log, instead of being displayed on the console. - Ensure that the whole connect/send/start button logic is sane. Right now it works when you perform the right sequence, but if you connect and disconnect, things can get confused. We'll need to improve how we store and send job files. Right now they are in ge->job_files[] and are always emptied on send. Keep them around? - Commit rate display is not enabled. - Group status reporting is not enabled. - Split gfio.c a bit. Add gfio/ sub directory, and split it into files based on functionality. It's already ~3000 lines long. - Attempt to ensure that we work with gtk 2.10 and newer. Right now the required version is ~2.18 (not quite known). fio-2.1.3/HOWTO000066400000000000000000002230171222032232000131060ustar00rootroot00000000000000Table of contents ----------------- 1. Overview 2. How fio works 3. Running fio 4. Job file format 5. Detailed list of parameters 6. Normal output 7. Terse output 8. Trace file format 9. CPU idleness profiling 1.0 Overview and history ------------------------ fio was originally written to save me the hassle of writing special test case programs when I wanted to test a specific workload, either for performance reasons or to find/reproduce a bug. The process of writing such a test app can be tiresome, especially if you have to do it often. Hence I needed a tool that would be able to simulate a given io workload without resorting to writing a tailored test case again and again. A test work load is difficult to define, though. There can be any number of processes or threads involved, and they can each be using their own way of generating io. You could have someone dirtying large amounts of memory in an memory mapped file, or maybe several threads issuing reads using asynchronous io. fio needed to be flexible enough to simulate both of these cases, and many more. 2.0 How fio works ----------------- The first step in getting fio to simulate a desired io workload, is writing a job file describing that specific setup. A job file may contain any number of threads and/or files - the typical contents of the job file is a global section defining shared parameters, and one or more job sections describing the jobs involved. When run, fio parses this file and sets everything up as described. If we break down a job from top to bottom, it contains the following basic parameters: IO type Defines the io pattern issued to the file(s). We may only be reading sequentially from this file(s), or we may be writing randomly. Or even mixing reads and writes, sequentially or randomly. Block size In how large chunks are we issuing io? This may be a single value, or it may describe a range of block sizes. IO size How much data are we going to be reading/writing. IO engine How do we issue io? We could be memory mapping the file, we could be using regular read/write, we could be using splice, async io, syslet, or even SG (SCSI generic sg). IO depth If the io engine is async, how large a queuing depth do we want to maintain? IO type Should we be doing buffered io, or direct/raw io? Num files How many files are we spreading the workload over. Num threads How many threads or processes should we spread this workload over. The above are the basic parameters defined for a workload, in addition there's a multitude of parameters that modify other aspects of how this job behaves. 3.0 Running fio --------------- See the README file for command line parameters, there are only a few of them. Running fio is normally the easiest part - you just give it the job file (or job files) as parameters: $ fio job_file and it will start doing what the job_file tells it to do. You can give more than one job file on the command line, fio will serialize the running of those files. Internally that is the same as using the 'stonewall' parameter described the the parameter section. If the job file contains only one job, you may as well just give the parameters on the command line. The command line parameters are identical to the job parameters, with a few extra that control global parameters (see README). For example, for the job file parameter iodepth=2, the mirror command line option would be --iodepth 2 or --iodepth=2. You can also use the command line for giving more than one job entry. For each --name option that fio sees, it will start a new job with that name. Command line entries following a --name entry will apply to that job, until there are no more entries or a new --name entry is seen. This is similar to the job file options, where each option applies to the current job until a new [] job entry is seen. fio does not need to run as root, except if the files or devices specified in the job section requires that. Some other options may also be restricted, such as memory locking, io scheduler switching, and decreasing the nice value. 4.0 Job file format ------------------- As previously described, fio accepts one or more job files describing what it is supposed to do. The job file format is the classic ini file, where the names enclosed in [] brackets define the job name. You are free to use any ascii name you want, except 'global' which has special meaning. A global section sets defaults for the jobs described in that file. A job may override a global section parameter, and a job file may even have several global sections if so desired. A job is only affected by a global section residing above it. If the first character in a line is a ';' or a '#', the entire line is discarded as a comment. So let's look at a really simple job file that defines two processes, each randomly reading from a 128MB file. ; -- start job file -- [global] rw=randread size=128m [job1] [job2] ; -- end job file -- As you can see, the job file sections themselves are empty as all the described parameters are shared. As no filename= option is given, fio makes up a filename for each of the jobs as it sees fit. On the command line, this job would look as follows: $ fio --name=global --rw=randread --size=128m --name=job1 --name=job2 Let's look at an example that has a number of processes writing randomly to files. ; -- start job file -- [random-writers] ioengine=libaio iodepth=4 rw=randwrite bs=32k direct=0 size=64m numjobs=4 ; -- end job file -- Here we have no global section, as we only have one job defined anyway. We want to use async io here, with a depth of 4 for each file. We also increased the buffer size used to 32KB and define numjobs to 4 to fork 4 identical jobs. The result is 4 processes each randomly writing to their own 64MB file. Instead of using the above job file, you could have given the parameters on the command line. For this case, you would specify: $ fio --name=random-writers --ioengine=libaio --iodepth=4 --rw=randwrite --bs=32k --direct=0 --size=64m --numjobs=4 4.1 Environment variables ------------------------- fio also supports environment variable expansion in job files. Any substring of the form "${VARNAME}" as part of an option value (in other words, on the right of the `='), will be expanded to the value of the environment variable called VARNAME. If no such environment variable is defined, or VARNAME is the empty string, the empty string will be substituted. As an example, let's look at a sample fio invocation and job file: $ SIZE=64m NUMJOBS=4 fio jobfile.fio ; -- start job file -- [random-writers] rw=randwrite size=${SIZE} numjobs=${NUMJOBS} ; -- end job file -- This will expand to the following equivalent job file at runtime: ; -- start job file -- [random-writers] rw=randwrite size=64m numjobs=4 ; -- end job file -- fio ships with a few example job files, you can also look there for inspiration. 4.2 Reserved keywords --------------------- Additionally, fio has a set of reserved keywords that will be replaced internally with the appropriate value. Those keywords are: $pagesize The architecture page size of the running system $mb_memory Megabytes of total memory in the system $ncpus Number of online available CPUs These can be used on the command line or in the job file, and will be automatically substituted with the current system values when the job is run. Simple math is also supported on these keywords, so you can perform actions like: size=8*$mb_memory and get that properly expanded to 8 times the size of memory in the machine. 5.0 Detailed list of parameters ------------------------------- This section describes in details each parameter associated with a job. Some parameters take an option of a given type, such as an integer or a string. The following types are used: str String. This is a sequence of alpha characters. time Integer with possible time suffix. In seconds unless otherwise specified, use eg 10m for 10 minutes. Accepts s/m/h for seconds, minutes, and hours. int SI integer. A whole number value, which may contain a suffix describing the base of the number. Accepted suffixes are k/m/g/t/p, meaning kilo, mega, giga, tera, and peta. The suffix is not case sensitive, and you may also include trailing 'b' (eg 'kb' is the same as 'k'). So if you want to specify 4096, you could either write out '4096' or just give 4k. The suffixes signify base 2 values, so 1024 is 1k and 1024k is 1m and so on, unless the suffix is explicitly set to a base 10 value using 'kib', 'mib', 'gib', etc. If that is the case, then 1000 is used as the multiplier. This can be handy for disks, since manufacturers generally use base 10 values when listing the capacity of a drive. If the option accepts an upper and lower range, use a colon ':' or minus '-' to separate such values. May also include a prefix to indicate numbers base. If 0x is used, the number is assumed to be hexadecimal. See irange. bool Boolean. Usually parsed as an integer, however only defined for true and false (1 and 0). irange Integer range with suffix. Allows value range to be given, such as 1024-4096. A colon may also be used as the separator, eg 1k:4k. If the option allows two sets of ranges, they can be specified with a ',' or '/' delimiter: 1k-4k/8k-32k. Also see int. float_list A list of floating numbers, separated by a ':' character. With the above in mind, here follows the complete list of fio job parameters. name=str ASCII name of the job. This may be used to override the name printed by fio for this job. Otherwise the job name is used. On the command line this parameter has the special purpose of also signaling the start of a new job. description=str Text description of the job. Doesn't do anything except dump this text description when this job is run. It's not parsed. directory=str Prefix filenames with this directory. Used to place files in a different location than "./". filename=str Fio normally makes up a filename based on the job name, thread number, and file number. If you want to share files between threads in a job or several jobs, specify a filename for each of them to override the default. If the ioengine used is 'net', the filename is the host, port, and protocol to use in the format of =host,port,protocol. See ioengine=net for more. If the ioengine is file based, you can specify a number of files by separating the names with a ':' colon. So if you wanted a job to open /dev/sda and /dev/sdb as the two working files, you would use filename=/dev/sda:/dev/sdb. On Windows, disk devices are accessed as \\.\PhysicalDrive0 for the first device, \\.\PhysicalDrive1 for the second etc. Note: Windows and FreeBSD prevent write access to areas of the disk containing in-use data (e.g. filesystems). If the wanted filename does need to include a colon, then escape that with a '\' character. For instance, if the filename is "/dev/dsk/foo@3,0:c", then you would use filename="/dev/dsk/foo@3,0\:c". '-' is a reserved name, meaning stdin or stdout. Which of the two depends on the read/write direction set. filename_format=str If sharing multiple files between jobs, it is usually necessary to have fio generate the exact names that you want. By default, fio will name a file based on the default file format specification of jobname.jobnumber.filenumber. With this option, that can be customized. Fio will recognize and replace the following keywords in this string: $jobname The name of the worker thread or process. $jobnum The incremental number of the worker thread or process. $filenum The incremental number of the file for that worker thread or process. To have dependent jobs share a set of files, this option can be set to have fio generate filenames that are shared between the two. For instance, if testfiles.$filenum is specified, file number 4 for any job will be named testfiles.4. The default of $jobname.$jobnum.$filenum will be used if no other format specifier is given. opendir=str Tell fio to recursively add any file it can find in this directory and down the file system tree. lockfile=str Fio defaults to not locking any files before it does IO to them. If a file or file descriptor is shared, fio can serialize IO to that file to make the end result consistent. This is usual for emulating real workloads that share files. The lock modes are: none No locking. The default. exclusive Only one thread/process may do IO, excluding all others. readwrite Read-write locking on the file. Many readers may access the file at the same time, but writes get exclusive access. readwrite=str rw=str Type of io pattern. Accepted values are: read Sequential reads write Sequential writes randwrite Random writes randread Random reads rw,readwrite Sequential mixed reads and writes randrw Random mixed reads and writes For the mixed io types, the default is to split them 50/50. For certain types of io the result may still be skewed a bit, since the speed may be different. It is possible to specify a number of IO's to do before getting a new offset, this is one by appending a ':' to the end of the string given. For a random read, it would look like 'rw=randread:8' for passing in an offset modifier with a value of 8. If the suffix is used with a sequential IO pattern, then the value specified will be added to the generated offset for each IO. For instance, using rw=write:4k will skip 4k for every write. It turns sequential IO into sequential IO with holes. See the 'rw_sequencer' option. rw_sequencer=str If an offset modifier is given by appending a number to the rw= line, then this option controls how that number modifies the IO offset being generated. Accepted values are: sequential Generate sequential offset identical Generate the same offset 'sequential' is only useful for random IO, where fio would normally generate a new random offset for every IO. If you append eg 8 to randread, you would get a new random offset for every 8 IO's. The result would be a seek for only every 8 IO's, instead of for every IO. Use rw=randread:8 to specify that. As sequential IO is already sequential, setting 'sequential' for that would not result in any differences. 'identical' behaves in a similar fashion, except it sends the same offset 8 number of times before generating a new offset. kb_base=int The base unit for a kilobyte. The defacto base is 2^10, 1024. Storage manufacturers like to use 10^3 or 1000 as a base ten unit instead, for obvious reasons. Allow values are 1024 or 1000, with 1024 being the default. unified_rw_reporting=bool Fio normally reports statistics on a per data direction basis, meaning that read, write, and trim are accounted and reported separately. If this option is set, the fio will sum the results and report them as "mixed" instead. randrepeat=bool For random IO workloads, seed the generator in a predictable way so that results are repeatable across repetitions. use_os_rand=bool Fio can either use the random generator supplied by the OS to generator random offsets, or it can use it's own internal generator (based on Tausworthe). Default is to use the internal generator, which is often of better quality and faster. fallocate=str Whether pre-allocation is performed when laying down files. Accepted values are: none Do not pre-allocate space posix Pre-allocate via posix_fallocate() keep Pre-allocate via fallocate() with FALLOC_FL_KEEP_SIZE set 0 Backward-compatible alias for 'none' 1 Backward-compatible alias for 'posix' May not be available on all supported platforms. 'keep' is only available on Linux.If using ZFS on Solaris this must be set to 'none' because ZFS doesn't support it. Default: 'posix'. fadvise_hint=bool By default, fio will use fadvise() to advise the kernel on what IO patterns it is likely to issue. Sometimes you want to test specific IO patterns without telling the kernel about it, in which case you can disable this option. If set, fio will use POSIX_FADV_SEQUENTIAL for sequential IO and POSIX_FADV_RANDOM for random IO. size=int The total size of file io for this job. Fio will run until this many bytes has been transferred, unless runtime is limited by other options (such as 'runtime', for instance). Unless specific nrfiles and filesize options are given, fio will divide this size between the available files specified by the job. If not set, fio will use the full size of the given files or devices. If the the files do not exist, size must be given. It is also possible to give size as a percentage between 1 and 100. If size=20% is given, fio will use 20% of the full size of the given files or devices. filesize=int Individual file sizes. May be a range, in which case fio will select sizes for files at random within the given range and limited to 'size' in total (if that is given). If not given, each created file is the same size. fill_device=bool fill_fs=bool Sets size to something really large and waits for ENOSPC (no space left on device) as the terminating condition. Only makes sense with sequential write. For a read workload, the mount point will be filled first then IO started on the result. This option doesn't make sense if operating on a raw device node, since the size of that is already known by the file system. Additionally, writing beyond end-of-device will not return ENOSPC there. blocksize=int bs=int The block size used for the io units. Defaults to 4k. Values can be given for both read and writes. If a single int is given, it will apply to both. If a second int is specified after a comma, it will apply to writes only. In other words, the format is either bs=read_and_write or bs=read,write,trim. bs=4k,8k will thus use 4k blocks for reads, 8k blocks for writes, and 8k for trims. You can terminate the list with a trailing comma. bs=4k,8k, would use the default value for trims.. If you only wish to set the write size, you can do so by passing an empty read size - bs=,8k will set 8k for writes and leave the read default value. blockalign=int ba=int At what boundary to align random IO offsets. Defaults to the same as 'blocksize' the minimum blocksize given. Minimum alignment is typically 512b for using direct IO, though it usually depends on the hardware block size. This option is mutually exclusive with using a random map for files, so it will turn off that option. blocksize_range=irange bsrange=irange Instead of giving a single block size, specify a range and fio will mix the issued io block sizes. The issued io unit will always be a multiple of the minimum value given (also see bs_unaligned). Applies to both reads and writes, however a second range can be given after a comma. See bs=. bssplit=str Sometimes you want even finer grained control of the block sizes issued, not just an even split between them. This option allows you to weight various block sizes, so that you are able to define a specific amount of block sizes issued. The format for this option is: bssplit=blocksize/percentage:blocksize/percentage for as many block sizes as needed. So if you want to define a workload that has 50% 64k blocks, 10% 4k blocks, and 40% 32k blocks, you would write: bssplit=4k/10:64k/50:32k/40 Ordering does not matter. If the percentage is left blank, fio will fill in the remaining values evenly. So a bssplit option like this one: bssplit=4k/50:1k/:32k/ would have 50% 4k ios, and 25% 1k and 32k ios. The percentages always add up to 100, if bssplit is given a range that adds up to more, it will error out. bssplit also supports giving separate splits to reads and writes. The format is identical to what bs= accepts. You have to separate the read and write parts with a comma. So if you want a workload that has 50% 2k reads and 50% 4k reads, while having 90% 4k writes and 10% 8k writes, you would specify: bssplit=2k/50:4k/50,4k/90,8k/10 blocksize_unaligned bs_unaligned If this option is given, any byte size value within bsrange may be used as a block range. This typically wont work with direct IO, as that normally requires sector alignment. bs_is_seq_rand If this option is set, fio will use the normal read,write blocksize settings as sequential,random instead. Any random read or write will use the WRITE blocksize settings, and any sequential read or write will use the READ blocksize setting. zero_buffers If this option is given, fio will init the IO buffers to all zeroes. The default is to fill them with random data. refill_buffers If this option is given, fio will refill the IO buffers on every submit. The default is to only fill it at init time and reuse that data. Only makes sense if zero_buffers isn't specified, naturally. If data verification is enabled, refill_buffers is also automatically enabled. scramble_buffers=bool If refill_buffers is too costly and the target is using data deduplication, then setting this option will slightly modify the IO buffer contents to defeat normal de-dupe attempts. This is not enough to defeat more clever block compression attempts, but it will stop naive dedupe of blocks. Default: true. buffer_compress_percentage=int If this is set, then fio will attempt to provide IO buffer content (on WRITEs) that compress to the specified level. Fio does this by providing a mix of random data and zeroes. Note that this is per block size unit, for file/disk wide compression level that matches this setting, you'll also want to set refill_buffers. buffer_compress_chunk=int See buffer_compress_percentage. This setting allows fio to manage how big the ranges of random data and zeroed data is. Without this set, fio will provide buffer_compress_percentage of blocksize random data, followed by the remaining zeroed. With this set to some chunk size smaller than the block size, fio can alternate random and zeroed data throughout the IO buffer. nrfiles=int Number of files to use for this job. Defaults to 1. openfiles=int Number of files to keep open at the same time. Defaults to the same as nrfiles, can be set smaller to limit the number simultaneous opens. file_service_type=str Defines how fio decides which file from a job to service next. The following types are defined: random Just choose a file at random. roundrobin Round robin over open files. This is the default. sequential Finish one file before moving on to the next. Multiple files can still be open depending on 'openfiles'. The string can have a number appended, indicating how often to switch to a new file. So if option random:4 is given, fio will switch to a new random file after 4 ios have been issued. ioengine=str Defines how the job issues io to the file. The following types are defined: sync Basic read(2) or write(2) io. lseek(2) is used to position the io location. psync Basic pread(2) or pwrite(2) io. vsync Basic readv(2) or writev(2) IO. psyncv Basic preadv(2) or pwritev(2) IO. libaio Linux native asynchronous io. Note that Linux may only support queued behaviour with non-buffered IO (set direct=1 or buffered=0). This engine defines engine specific options. posixaio glibc posix asynchronous io. solarisaio Solaris native asynchronous io. windowsaio Windows native asynchronous io. mmap File is memory mapped and data copied to/from using memcpy(3). splice splice(2) is used to transfer the data and vmsplice(2) to transfer data from user space to the kernel. syslet-rw Use the syslet system calls to make regular read/write async. sg SCSI generic sg v3 io. May either be synchronous using the SG_IO ioctl, or if the target is an sg character device we use read(2) and write(2) for asynchronous io. null Doesn't transfer any data, just pretends to. This is mainly used to exercise fio itself and for debugging/testing purposes. net Transfer over the network to given host:port. Depending on the protocol used, the hostname, port, listen and filename options are used to specify what sort of connection to make, while the protocol option determines which protocol will be used. This engine defines engine specific options. netsplice Like net, but uses splice/vmsplice to map data and send/receive. This engine defines engine specific options. cpuio Doesn't transfer any data, but burns CPU cycles according to the cpuload= and cpucycle= options. Setting cpuload=85 will cause that job to do nothing but burn 85% of the CPU. In case of SMP machines, use numjobs= to get desired CPU usage, as the cpuload only loads a single CPU at the desired rate. guasi The GUASI IO engine is the Generic Userspace Asyncronous Syscall Interface approach to async IO. See http://www.xmailserver.org/guasi-lib.html for more info on GUASI. rdma The RDMA I/O engine supports both RDMA memory semantics (RDMA_WRITE/RDMA_READ) and channel semantics (Send/Recv) for the InfiniBand, RoCE and iWARP protocols. falloc IO engine that does regular fallocate to simulate data transfer as fio ioengine. DDIR_READ does fallocate(,mode = keep_size,) DDIR_WRITE does fallocate(,mode = 0) DDIR_TRIM does fallocate(,mode = punch_hole) e4defrag IO engine that does regular EXT4_IOC_MOVE_EXT ioctls to simulate defragment activity in request to DDIR_WRITE event external Prefix to specify loading an external IO engine object file. Append the engine filename, eg ioengine=external:/tmp/foo.o to load ioengine foo.o in /tmp. iodepth=int This defines how many io units to keep in flight against the file. The default is 1 for each file defined in this job, can be overridden with a larger value for higher concurrency. Note that increasing iodepth beyond 1 will not affect synchronous ioengines (except for small degress when verify_async is in use). Even async engines may 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. Keep an eye on the IO depth distribution in the fio output to verify that the achieved depth is as expected. Default: 1. iodepth_batch_submit=int iodepth_batch=int This defines how many pieces of IO to submit at once. It defaults to 1 which means that we submit each IO as soon as it is available, but can be raised to submit bigger batches of IO at the time. iodepth_batch_complete=int This defines how many pieces of IO to retrieve at once. It defaults to 1 which means that we'll ask for a minimum of 1 IO in the retrieval process from the kernel. The IO retrieval will go on until we hit the limit set by iodepth_low. If this variable is set to 0, then fio will always check for completed events before queuing more IO. This helps reduce IO latency, at the cost of more retrieval system calls. iodepth_low=int The low water mark indicating when to start filling the queue again. Defaults to the same as iodepth, meaning that fio will attempt to keep the queue full at all times. If iodepth is set to eg 16 and iodepth_low is set to 4, then after fio has filled the queue of 16 requests, it will let the depth drain down to 4 before starting to fill it again. direct=bool If value is true, use non-buffered io. This is usually O_DIRECT. Note that ZFS on Solaris doesn't support direct io. On Windows the synchronous ioengines don't support direct io. buffered=bool If value is true, use buffered io. This is the opposite of the 'direct' option. Defaults to true. offset=int Start io at the given offset in the file. The data before the given offset will not be touched. This effectively caps the file size at real_size - offset. offset_increment=int If this is provided, then the real offset becomes the offset + offset_increment * thread_number, where the thread number is a counter that starts at 0 and is incremented for each job. This option is useful if there are several jobs which are intended to operate on a file in parallel in disjoint segments, with even spacing between the starting points. number_ios=int Fio will normally perform IOs until it has exhausted the size of the region set by size=, or if it exhaust the allocated time (or hits an error condition). With this setting, the range/size can be set independently of the number of IOs to perform. When fio reaches this number, it will exit normally and report status. fsync=int If writing to a file, issue a sync of the dirty data for every number of blocks given. For example, if you give 32 as a parameter, fio will sync the file for every 32 writes issued. If fio is using non-buffered io, we may not sync the file. The exception is the sg io engine, which synchronizes the disk cache anyway. fdatasync=int Like fsync= but uses fdatasync() to only sync data and not metadata blocks. In FreeBSD and Windows there is no fdatasync(), this falls back to using fsync() sync_file_range=str:val Use sync_file_range() for every 'val' number of write operations. Fio will track range of writes that have happened since the last sync_file_range() call. 'str' can currently be one or more of: wait_before SYNC_FILE_RANGE_WAIT_BEFORE write SYNC_FILE_RANGE_WRITE wait_after SYNC_FILE_RANGE_WAIT_AFTER So if you do sync_file_range=wait_before,write:8, fio would use SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE for every 8 writes. Also see the sync_file_range(2) man page. This option is Linux specific. overwrite=bool If true, writes to a file will always overwrite existing data. If the file doesn't already exist, it will be created before the write phase begins. If the file exists and is large enough for the specified write phase, nothing will be done. end_fsync=bool If true, fsync file contents when a write stage has completed. fsync_on_close=bool If true, fio will fsync() a dirty file on close. This differs from end_fsync in that it will happen on every file close, not just at the end of the job. rwmixread=int How large a percentage of the mix should be reads. rwmixwrite=int How large a percentage of the mix should be writes. If both rwmixread and rwmixwrite is given and the values do not add up to 100%, the latter of the two will be used to override the first. This may interfere with a given rate setting, if fio is asked to limit reads or writes to a certain rate. If that is the case, then the distribution may be skewed. random_distribution=str:float By default, fio will use a completely uniform random distribution when asked to perform random IO. Sometimes it is useful to skew the distribution in specific ways, ensuring that some parts of the data is more hot than others. fio includes the following distribution models: random Uniform random distribution zipf Zipf distribution pareto Pareto distribution When using a zipf or pareto distribution, an input value is also needed to define the access pattern. For zipf, this is the zipf theta. For pareto, it's the pareto power. Fio includes a test program, genzipf, that can be used visualize what the given input values will yield in terms of hit rates. If you wanted to use zipf with a theta of 1.2, you would use random_distribution=zipf:1.2 as the option. If a non-uniform model is used, fio will disable use of the random map. percentage_random=int For a random workload, set how big a percentage should be random. This defaults to 100%, in which case the workload is fully random. It can be set from anywhere from 0 to 100. Setting it to 0 would make the workload fully sequential. Any setting in between will result in a random mix of sequential and random IO, at the given percentages. It is possible to set different values for reads, writes, and trim. To do so, simply use a comma separated list. See blocksize. norandommap Normally fio will cover every block of the file when doing random IO. If this option is given, fio will just get a new random offset without looking at past io history. This means that some blocks may not be read or written, and that some blocks may be read/written more than once. This option is mutually exclusive with verify= if and only if multiple blocksizes (via bsrange=) are used, since fio only tracks complete rewrites of blocks. softrandommap=bool See norandommap. If fio runs with the random block map enabled and it fails to allocate the map, if this option is set it will continue without a random block map. As coverage will not be as complete as with random maps, this option is disabled by default. random_generator=str Fio supports the following engines for generating IO offsets for random IO: tausworthe Strong 2^88 cycle random number generator lfsr Linear feedback shift register generator Tausworthe is a strong random number generator, but it requires tracking on the side if we want to ensure that blocks are only read or written once. LFSR guarantees that we never generate the same offset twice, and it's also less computationally expensive. It's not a true random generator, however, though for IO purposes it's typically good enough. LFSR only works with single block sizes, not with workloads that use multiple block sizes. If used with such a workload, fio may read or write some blocks multiple times. nice=int Run the job with the given nice value. See man nice(2). prio=int Set the io priority value of this job. Linux limits us to a positive value between 0 and 7, with 0 being the highest. See man ionice(1). prioclass=int Set the io priority class. See man ionice(1). thinktime=int Stall the job x microseconds after an io has completed before issuing the next. May be used to simulate processing being done by an application. See thinktime_blocks and thinktime_spin. thinktime_spin=int Only valid if thinktime is set - pretend to spend CPU time doing something with the data received, before falling back to sleeping for the rest of the period specified by thinktime. thinktime_blocks=int Only valid if thinktime is set - control how many blocks to issue, before waiting 'thinktime' usecs. If not set, defaults to 1 which will make fio wait 'thinktime' usecs after every block. This effectively makes any queue depth setting redundant, since no more than 1 IO will be queued before we have to complete it and do our thinktime. In other words, this setting effectively caps the queue depth if the latter is larger. rate=int Cap the bandwidth used by this job. The number is in bytes/sec, the normal suffix rules apply. You can use rate=500k to limit reads and writes to 500k each, or you can specify read and writes separately. Using rate=1m,500k would limit reads to 1MB/sec and writes to 500KB/sec. Capping only reads or writes can be done with rate=,500k or rate=500k,. The former will only limit writes (to 500KB/sec), the latter will only limit reads. ratemin=int Tell fio to do whatever it can to maintain at least this bandwidth. Failing to meet this requirement, will cause the job to exit. The same format as rate is used for read vs write separation. rate_iops=int Cap the bandwidth to this number of IOPS. Basically the same as rate, just specified independently of bandwidth. If the job is given a block size range instead of a fixed value, the smallest block size is used as the metric. The same format as rate is used for read vs write seperation. rate_iops_min=int If fio doesn't meet this rate of IO, it will cause the job to exit. The same format as rate is used for read vs write seperation. max_latency=int If set, fio will exit the job if it exceeds this maximum latency. It will exit with an ETIME error. ratecycle=int Average bandwidth for 'rate' and 'ratemin' over this number of milliseconds. cpumask=int Set the CPU affinity of this job. The parameter given is a bitmask of allowed CPU's the job may run on. So if you want the allowed CPUs to be 1 and 5, you would pass the decimal value of (1 << 1 | 1 << 5), or 34. See man sched_setaffinity(2). This may not work on all supported operating systems or kernel versions. This option doesn't work well for a higher CPU count than what you can store in an integer mask, so it can only control cpus 1-32. For boxes with larger CPU counts, use cpus_allowed. cpus_allowed=str Controls the same options as cpumask, but it allows a text setting of the permitted CPUs instead. So to use CPUs 1 and 5, you would specify cpus_allowed=1,5. This options also allows a range of CPUs. Say you wanted a binding to CPUs 1, 5, and 8-15, you would set cpus_allowed=1,5,8-15. numa_cpu_nodes=str Set this job running on spcified NUMA nodes' CPUs. The arguments allow comma delimited list of cpu numbers, A-B ranges, or 'all'. Note, to enable numa options support, fio must be built on a system with libnuma-dev(el) installed. numa_mem_policy=str Set this job's memory policy and corresponding NUMA nodes. Format of the argements: [:] `mode' is one of the following memory policy: default, prefer, bind, interleave, local For `default' and `local' memory policy, no node is needed to be specified. For `prefer', only one node is allowed. For `bind' and `interleave', it allow comma delimited list of numbers, A-B ranges, or 'all'. startdelay=time Start this job the specified number of seconds after fio has started. Only useful if the job file contains several jobs, and you want to delay starting some jobs to a certain time. runtime=time Tell fio to terminate processing after the specified number of seconds. It can be quite hard to determine for how long a specified job will run, so this parameter is handy to cap the total runtime to a given time. time_based If set, fio will run for the duration of the runtime specified even if the file(s) are completely read or written. It will simply loop over the same workload as many times as the runtime allows. ramp_time=time If set, fio will run the specified workload for this amount of time before logging any performance numbers. Useful for letting performance settle before logging results, thus minimizing the runtime required for stable results. Note that the ramp_time is considered lead in time for a job, thus it will increase the total runtime if a special timeout or runtime is specified. invalidate=bool Invalidate the buffer/page cache parts for this file prior to starting io. Defaults to true. sync=bool Use sync io for buffered writes. For the majority of the io engines, this means using O_SYNC. iomem=str mem=str Fio can use various types of memory as the io unit buffer. The allowed values are: malloc Use memory from malloc(3) as the buffers. shm Use shared memory as the buffers. Allocated through shmget(2). shmhuge Same as shm, but use huge pages as backing. mmap Use mmap to allocate buffers. May either be anonymous memory, or can be file backed if a filename is given after the option. The format is mem=mmap:/path/to/file. mmaphuge Use a memory mapped huge file as the buffer backing. Append filename after mmaphuge, ala mem=mmaphuge:/hugetlbfs/file The area allocated is a function of the maximum allowed bs size for the job, multiplied by the io depth given. Note that for shmhuge and mmaphuge to work, the system must have free huge pages allocated. This can normally be checked and set by reading/writing /proc/sys/vm/nr_hugepages on a Linux system. Fio assumes a huge page is 4MB in size. So to calculate the number of huge pages you need for a given job file, add up the io depth of all jobs (normally one unless iodepth= is used) and multiply by the maximum bs set. Then divide that number by the huge page size. You can see the size of the huge pages in /proc/meminfo. If no huge pages are allocated by having a non-zero number in nr_hugepages, using mmaphuge or shmhuge will fail. Also see hugepage-size. mmaphuge also needs to have hugetlbfs mounted and the file location should point there. So if it's mounted in /huge, you would use mem=mmaphuge:/huge/somefile. iomem_align=int This indiciates the memory alignment of the IO memory buffers. Note that the given alignment is applied to the first IO unit buffer, if using iodepth the alignment of the following buffers are given by the bs used. In other words, if using a bs that is a multiple of the page sized in the system, all buffers will be aligned to this value. If using a bs that is not page aligned, the alignment of subsequent IO memory buffers is the sum of the iomem_align and bs used. hugepage-size=int Defines the size of a huge page. Must at least be equal to the system setting, see /proc/meminfo. Defaults to 4MB. Should probably always be a multiple of megabytes, so using hugepage-size=Xm is the preferred way to set this to avoid setting a non-pow-2 bad value. exitall When one job finishes, terminate the rest. The default is to wait for each job to finish, sometimes that is not the desired action. bwavgtime=int Average the calculated bandwidth over the given time. Value is specified in milliseconds. iopsavgtime=int Average the calculated IOPS over the given time. Value is specified in milliseconds. create_serialize=bool If true, serialize the file creating for the jobs. This may be handy to avoid interleaving of data files, which may greatly depend on the filesystem used and even the number of processors in the system. create_fsync=bool fsync the data file after creation. This is the default. create_on_open=bool Don't pre-setup the files for IO, just create open() when it's time to do IO to that file. create_only=bool If true, fio will only run the setup phase of the job. If files need to be laid out or updated on disk, only that will be done. The actual job contents are not executed. pre_read=bool If this is given, files will be pre-read into memory before starting the given IO operation. This will also clear the 'invalidate' flag, since it is pointless to pre-read and then drop the cache. This will only work for IO engines that are seekable, since they allow you to read the same data multiple times. Thus it will not work on eg network or splice IO. unlink=bool Unlink the job files when done. Not the default, as repeated runs of that job would then waste time recreating the file set again and again. loops=int Run the specified number of iterations of this job. Used to repeat the same workload a given number of times. Defaults to 1. do_verify=bool Run the verify phase after a write phase. Only makes sense if verify is set. Defaults to 1. verify=str If writing to a file, fio can verify the file contents after each iteration of the job. The allowed values are: md5 Use an md5 sum of the data area and store it in the header of each block. crc64 Use an experimental crc64 sum of the data area and store it in the header of each block. crc32c Use a crc32c sum of the data area and store it in the header of each block. crc32c-intel Use hardware assisted crc32c calcuation provided on SSE4.2 enabled processors. Falls back to regular software crc32c, if not supported by the system. crc32 Use a crc32 sum of the data area and store it in the header of each block. crc16 Use a crc16 sum of the data area and store it in the header of each block. crc7 Use a crc7 sum of the data area and store it in the header of each block. sha512 Use sha512 as the checksum function. sha256 Use sha256 as the checksum function. sha1 Use optimized sha1 as the checksum function. meta Write extra information about each io (timestamp, block number etc.). The block number is verified. See also verify_pattern. null Only pretend to verify. Useful for testing internals with ioengine=null, not for much else. This option can be used for repeated burn-in tests of a system to make sure that the written data is also correctly read back. If the data direction given is a read or random read, fio will assume that it should verify a previously written file. If the data direction includes any form of write, the verify will be of the newly written data. verifysort=bool If set, fio will sort written verify blocks when it deems it faster to read them back in a sorted manner. This is often the case when overwriting an existing file, since the blocks are already laid out in the file system. You can ignore this option unless doing huge amounts of really fast IO where the red-black tree sorting CPU time becomes significant. verify_offset=int Swap the verification header with data somewhere else in the block before writing. Its swapped back before verifying. verify_interval=int Write the verification header at a finer granularity than the blocksize. It will be written for chunks the size of header_interval. blocksize should divide this evenly. verify_pattern=str If set, fio will fill the io buffers with this pattern. Fio defaults to filling with totally random bytes, but sometimes it's interesting to fill with a known pattern for io verification purposes. Depending on the width of the pattern, fio will fill 1/2/3/4 bytes of the buffer at the time(it can be either a decimal or a hex number). The verify_pattern if larger than a 32-bit quantity has to be a hex number that starts with either "0x" or "0X". Use with verify=meta. verify_fatal=bool Normally fio will keep checking the entire contents before quitting on a block verification failure. If this option is set, fio will exit the job on the first observed failure. verify_dump=bool If set, dump the contents of both the original data block and the data block we read off disk to files. This allows later analysis to inspect just what kind of data corruption occurred. Off by default. verify_async=int Fio will normally verify IO inline from the submitting thread. This option takes an integer describing how many async offload threads to create for IO verification instead, causing fio to offload the duty of verifying IO contents to one or more separate threads. If using this offload option, even sync IO engines can benefit from using an iodepth setting higher than 1, as it allows them to have IO in flight while verifies are running. verify_async_cpus=str Tell fio to set the given CPU affinity on the async IO verification threads. See cpus_allowed for the format used. verify_backlog=int Fio will normally verify the written contents of a job that utilizes verify once that job has completed. In other words, everything is written then everything is read back and verified. You may want to verify continually instead for a variety of reasons. Fio stores the meta data associated with an IO block in memory, so for large verify workloads, quite a bit of memory would be used up holding this meta data. If this option is enabled, fio will write only N blocks before verifying these blocks. will verify the previously written blocks before continuing to write new ones. verify_backlog_batch=int Control how many blocks fio will verify if verify_backlog is set. If not set, will default to the value of verify_backlog (meaning the entire queue is read back and verified). If verify_backlog_batch is less than verify_backlog then not all blocks will be verified, if verify_backlog_batch is larger than verify_backlog, some blocks will be verified more than once. stonewall wait_for_previous Wait for preceeding jobs in the job file to exit, before starting this one. Can be used to insert serialization points in the job file. A stone wall also implies starting a new reporting group. new_group Start a new reporting group. See: group_reporting. numjobs=int Create the specified number of clones of this job. May be used to setup a larger number of threads/processes doing the same thing. Each thread is reported separately; to see statistics for all clones as a whole, use group_reporting in conjunction with new_group. group_reporting It may sometimes be interesting to display statistics for groups of jobs as a whole instead of for each individual job. This is especially true if 'numjobs' is used; looking at individual thread/process output quickly becomes unwieldy. To see the final report per-group instead of per-job, use 'group_reporting'. Jobs in a file will be part of the same reporting group, unless if separated by a stonewall, or by using 'new_group'. thread fio defaults to forking jobs, however if this option is given, fio will use pthread_create(3) to create threads instead. zonesize=int Divide a file into zones of the specified size. See zoneskip. zoneskip=int Skip the specified number of bytes when zonesize data has been read. The two zone options can be used to only do io on zones of a file. write_iolog=str Write the issued io patterns to the specified file. See read_iolog. Specify a separate file for each job, otherwise the iologs will be interspersed and the file may be corrupt. read_iolog=str Open an iolog with the specified file name and replay the io patterns it contains. This can be used to store a workload and replay it sometime later. The iolog given may also be a blktrace binary file, which allows fio to replay a workload captured by blktrace. See blktrace for how to capture such logging data. For blktrace replay, the file needs to be turned into a blkparse binary data file first (blkparse -o /dev/null -d file_for_fio.bin). replay_no_stall=int When replaying I/O with read_iolog the default behavior is to attempt to respect the time stamps within the log and replay them with the appropriate delay between IOPS. By setting this variable fio will not respect the timestamps and attempt to replay them as fast as possible while still respecting ordering. The result is the same I/O pattern to a given device, but different timings. replay_redirect=str While replaying I/O patterns using read_iolog the default behavior is to replay the IOPS onto the major/minor device that each IOP was recorded from. This is sometimes undesireable because on a different machine those major/minor numbers can map to a different device. Changing hardware on the same system can also result in a different major/minor mapping. Replay_redirect causes all IOPS to be replayed onto the single specified device regardless of the device it was recorded from. i.e. replay_redirect=/dev/sdc would cause all IO in the blktrace to be replayed onto /dev/sdc. This means multiple devices will be replayed onto a single, if the trace contains multiple devices. If you want multiple devices to be replayed concurrently to multiple redirected devices you must blkparse your trace into separate traces and replay them with independent fio invocations. Unfortuantely this also breaks the strict time ordering between multiple device accesses. write_bw_log=str If given, write a bandwidth log of the jobs in this job file. Can be used to store data of the bandwidth of the jobs in their lifetime. The included fio_generate_plots script uses gnuplot to turn these text files into nice graphs. See write_lat_log for behaviour of given filename. For this option, the suffix is _bw.log. write_lat_log=str Same as write_bw_log, except that this option stores io submission, completion, and total latencies instead. If no filename is given with this option, the default filename of "jobname_type.log" is used. Even if the filename is given, fio will still append the type of log. So if one specifies write_lat_log=foo The actual log names will be foo_slat.log, foo_clat.log, and foo_lat.log. This helps fio_generate_plot fine the logs automatically. write_bw_log=str If given, write an IOPS log of the jobs in this job file. See write_bw_log. write_iops_log=str Same as write_bw_log, but writes IOPS. If no filename is given with this option, the default filename of "jobname_type.log" is used. Even if the filename is given, fio will still append the type of log. log_avg_msec=int By default, fio will log an entry in the iops, latency, or bw log for every IO that completes. When writing to the disk log, that can quickly grow to a very large size. Setting this option makes fio average the each log entry over the specified period of time, reducing the resolution of the log. Defaults to 0. lockmem=int Pin down the specified amount of memory with mlock(2). Can potentially be used instead of removing memory or booting with less memory to simulate a smaller amount of memory. The amount specified is per worker. exec_prerun=str Before running this job, issue the command specified through system(3). Output is redirected in a file called jobname.prerun.txt. exec_postrun=str After the job completes, issue the command specified though system(3). Output is redirected in a file called jobname.postrun.txt. ioscheduler=str Attempt to switch the device hosting the file to the specified io scheduler before running. disk_util=bool Generate disk utilization statistics, if the platform supports it. Defaults to on. disable_lat=bool Disable measurements of total latency numbers. Useful only for cutting back the number of calls to gettimeofday, as that does impact performance at really high IOPS rates. Note that to really get rid of a large amount of these calls, this option must be used with disable_slat and disable_bw as well. disable_clat=bool Disable measurements of completion latency numbers. See disable_lat. disable_slat=bool Disable measurements of submission latency numbers. See disable_slat. disable_bw=bool Disable measurements of throughput/bandwidth numbers. See disable_lat. clat_percentiles=bool Enable the reporting of percentiles of completion latencies. percentile_list=float_list Overwrite the default list of percentiles for completion latencies. Each number is a floating number in the range (0,100], and the maximum length of the list is 20. Use ':' to separate the numbers, and list the numbers in ascending order. For example, --percentile_list=99.5:99.9 will cause fio to report the values of completion latency below which 99.5% and 99.9% of the observed latencies fell, respectively. clocksource=str Use the given clocksource as the base of timing. The supported options are: gettimeofday gettimeofday(2) clock_gettime clock_gettime(2) cpu Internal CPU clock source cpu is the preferred clocksource if it is reliable, as it is very fast (and fio is heavy on time calls). Fio will automatically use this clocksource if it's supported and considered reliable on the system it is running on, unless another clocksource is specifically set. For x86/x86-64 CPUs, this means supporting TSC Invariant. gtod_reduce=bool Enable all of the gettimeofday() reducing options (disable_clat, disable_slat, disable_bw) plus reduce precision of the timeout somewhat to really shrink the gettimeofday() call count. With this option enabled, we only do about 0.4% of the gtod() calls we would have done if all time keeping was enabled. gtod_cpu=int Sometimes it's cheaper to dedicate a single thread of execution to just getting the current time. Fio (and databases, for instance) are very intensive on gettimeofday() calls. With this option, you can set one CPU aside for doing nothing but logging current time to a shared memory location. Then the other threads/processes that run IO workloads need only copy that segment, instead of entering the kernel with a gettimeofday() call. The CPU set aside for doing these time calls will be excluded from other uses. Fio will manually clear it from the CPU mask of other jobs. continue_on_error=str Normally fio will exit the job on the first observed failure. If this option is set, fio will continue the job when there is a 'non-fatal error' (EIO or EILSEQ) until the runtime is exceeded or the I/O size specified is completed. If this option is used, there are two more stats that are appended, the total error count and the first error. The error field given in the stats is the first error that was hit during the run. The allowed values are: none Exit on any IO or verify errors. read Continue on read errors, exit on all others. write Continue on write errors, exit on all others. io Continue on any IO error, exit on all others. verify Continue on verify errors, exit on all others. all Continue on all errors. 0 Backward-compatible alias for 'none'. 1 Backward-compatible alias for 'all'. ignore_error=str Sometimes you want to ignore some errors during test in that case you can specify error list for each error type. ignore_error=READ_ERR_LIST,WRITE_ERR_LIST,VERIFY_ERR_LIST errors for given error type is separated with ':'. Error may be symbol ('ENOSPC', 'ENOMEM') or integer. Example: ignore_error=EAGAIN,ENOSPC:122 This option will ignore EAGAIN from READ, and ENOSPC and 122(EDQUOT) from WRITE. error_dump=bool If set dump every error even if it is non fatal, true by default. If disabled only fatal error will be dumped cgroup=str Add job to this control group. If it doesn't exist, it will be created. The system must have a mounted cgroup blkio mount point for this to work. If your system doesn't have it mounted, you can do so with: # mount -t cgroup -o blkio none /cgroup cgroup_weight=int Set the weight of the cgroup to this value. See the documentation that comes with the kernel, allowed values are in the range of 100..1000. cgroup_nodelete=bool Normally fio will delete the cgroups it has created after the job completion. To override this behavior and to leave cgroups around after the job completion, set cgroup_nodelete=1. This can be useful if one wants to inspect various cgroup files after job completion. Default: false uid=int Instead of running as the invoking user, set the user ID to this value before the thread/process does any work. gid=int Set group ID, see uid. flow_id=int The ID of the flow. If not specified, it defaults to being a global flow. See flow. flow=int Weight in token-based flow control. If this value is used, then there is a 'flow counter' which is used to regulate the proportion of activity between two or more jobs. fio attempts to keep this flow counter near zero. The 'flow' parameter stands for how much should be added or subtracted to the flow counter on each iteration of the main I/O loop. That is, if one job has flow=8 and another job has flow=-1, then there will be a roughly 1:8 ratio in how much one runs vs the other. flow_watermark=int The maximum value that the absolute value of the flow counter is allowed to reach before the job must wait for a lower value of the counter. flow_sleep=int The period of time, in microseconds, to wait after the flow watermark has been exceeded before retrying operations In addition, there are some parameters which are only valid when a specific ioengine is in use. These are used identically to normal parameters, with the caveat that when used on the command line, they must come after the ioengine that defines them is selected. [libaio] userspace_reap Normally, with the libaio engine in use, fio will use the io_getevents system call to reap newly returned events. With this flag turned on, the AIO ring will be read directly from user-space to reap events. The reaping mode is only enabled when polling for a minimum of 0 events (eg when iodepth_batch_complete=0). [cpu] cpuload=int Attempt to use the specified percentage of CPU cycles. [cpu] cpuchunks=int Split the load into cycles of the given time. In microseconds. [netsplice] hostname=str [net] hostname=str The host name or IP address to use for TCP or UDP based IO. If the job is a TCP listener or UDP reader, the hostname is not used and must be omitted unless it is a valid UDP multicast address. [netsplice] port=int [net] port=int The TCP or UDP port to bind to or connect to. [netsplice] interface=str [net] interface=str The IP address of the network interface used to send or receive UDP multicast [netsplice] ttl=int [net] ttl=int Time-to-live value for outgoing UDP multicast packets. Default: 1 [netsplice] nodelay=bool [net] nodelay=bool Set TCP_NODELAY on TCP connections. [netsplice] protocol=str [netsplice] proto=str [net] protocol=str [net] proto=str The network protocol to use. Accepted values are: tcp Transmission control protocol udp User datagram protocol unix UNIX domain socket When the protocol is TCP or UDP, the port must also be given, as well as the hostname if the job is a TCP listener or UDP reader. For unix sockets, the normal filename option should be used and the port is invalid. [net] listen For TCP network connections, tell fio to listen for incoming connections rather than initiating an outgoing connection. The hostname must be omitted if this option is used. [net] pingpong Normaly a network writer will just continue writing data, and a network reader will just consume packages. If pingpong=1 is set, a writer will send its normal payload to the reader, then wait for the reader to send the same payload back. This allows fio to measure network latencies. The submission and completion latencies then measure local time spent sending or receiving, and the completion latency measures how long it took for the other end to receive and send back. For UDP multicast traffic pingpong=1 should only be set for a single reader when multiple readers are listening to the same address. [e4defrag] donorname=str File will be used as a block donor(swap extents between files) [e4defrag] inplace=int Configure donor file blocks allocation strategy 0(default): Preallocate donor's file on init 1 : allocate space immidietly inside defragment event, and free right after event 6.0 Interpreting the output --------------------------- fio spits out a lot of output. While running, fio will display the status of the jobs created. An example of that would be: Threads: 1: [_r] [24.8% done] [ 13509/ 8334 kb/s] [eta 00h:01m:31s] The characters inside the square brackets denote the current status of each thread. The possible values (in typical life cycle order) are: Idle Run ---- --- P Thread setup, but not started. C Thread created. I Thread initialized, waiting or generating necessary data. p Thread running pre-reading file(s). R Running, doing sequential reads. r Running, doing random reads. W Running, doing sequential writes. w Running, doing random writes. M Running, doing mixed sequential reads/writes. m Running, doing mixed random reads/writes. F Running, currently waiting for fsync() V Running, doing verification of written data. E Thread exited, not reaped by main thread yet. _ Thread reaped, or X Thread reaped, exited with an error. K Thread reaped, exited due to signal. The other values are fairly self explanatory - number of threads currently running and doing io, rate of io since last check (read speed listed first, then write speed), and the estimated completion percentage and time for the running group. It's impossible to estimate runtime of the following groups (if any). Note that the string is displayed in order, so it's possible to tell which of the jobs are currently doing what. The first character is the first job defined in the job file, and so forth. When fio is done (or interrupted by ctrl-c), it will show the data for each thread, group of threads, and disks in that order. For each data direction, the output looks like: Client1 (g=0): err= 0: write: io= 32MB, bw= 666KB/s, iops=89 , runt= 50320msec slat (msec): min= 0, max= 136, avg= 0.03, stdev= 1.92 clat (msec): min= 0, max= 631, avg=48.50, stdev=86.82 bw (KB/s) : min= 0, max= 1196, per=51.00%, avg=664.02, stdev=681.68 cpu : usr=1.49%, sys=0.25%, ctx=7969, majf=0, minf=17 IO depths : 1=0.1%, 2=0.3%, 4=0.5%, 8=99.0%, 16=0.0%, 32=0.0%, >32=0.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% issued r/w: total=0/32768, short=0/0 lat (msec): 2=1.6%, 4=0.0%, 10=3.2%, 20=12.8%, 50=38.4%, 100=24.8%, lat (msec): 250=15.2%, 500=0.0%, 750=0.0%, 1000=0.0%, >=2048=0.0% The client number is printed, along with the group id and error of that thread. Below is the io statistics, here for writes. In the order listed, they denote: io= Number of megabytes io performed bw= Average bandwidth rate iops= Average IOs performed per second runt= The runtime of that thread slat= Submission latency (avg being the average, stdev being the standard deviation). This is the time it took to submit the io. For sync io, the slat is really the completion latency, since queue/complete is one operation there. This value can be in milliseconds or microseconds, fio will choose the most appropriate base and print that. In the example above, milliseconds is the best scale. Note: in --minimal mode latencies are always expressed in microseconds. clat= Completion latency. Same names as slat, this denotes the time from submission to completion of the io pieces. For sync io, clat will usually be equal (or very close) to 0, as the time from submit to complete is basically just CPU time (io has already been done, see slat explanation). bw= Bandwidth. Same names as the xlat stats, but also includes an approximate percentage of total aggregate bandwidth this thread received in this group. This last value is only really useful if the threads in this group are on the same disk, since they are then competing for disk access. cpu= CPU usage. User and system time, along with the number of context switches this thread went through, usage of system and user time, and finally the number of major and minor page faults. IO depths= The distribution of io depths over the job life time. The numbers are divided into powers of 2, so for example the 16= entries includes depths up to that value but higher than the previous entry. In other words, it covers the range from 16 to 31. IO submit= How many pieces of IO were submitting in a single submit call. Each entry denotes that amount and below, until the previous entry - eg, 8=100% mean that we submitted anywhere in between 5-8 ios per submit call. IO complete= Like the above submit number, but for completions instead. IO issued= The number of read/write requests issued, and how many of them were short. IO latencies= The distribution of IO completion latencies. This is the time from when IO leaves fio and when it gets completed. The numbers follow the same pattern as the IO depths, meaning that 2=1.6% means that 1.6% of the IO completed within 2 msecs, 20=12.8% means that 12.8% of the IO took more than 10 msecs, but less than (or equal to) 20 msecs. After each client has been listed, the group statistics are printed. They will look like this: Run status group 0 (all jobs): READ: io=64MB, aggrb=22178, minb=11355, maxb=11814, mint=2840msec, maxt=2955msec WRITE: io=64MB, aggrb=1302, minb=666, maxb=669, mint=50093msec, maxt=50320msec For each data direction, it prints: io= Number of megabytes io performed. aggrb= Aggregate bandwidth of threads in this group. minb= The minimum average bandwidth a thread saw. maxb= The maximum average bandwidth a thread saw. mint= The smallest runtime of the threads in that group. maxt= The longest runtime of the threads in that group. And finally, the disk statistics are printed. They will look like this: Disk stats (read/write): sda: ios=16398/16511, merge=30/162, ticks=6853/819634, in_queue=826487, util=100.00% Each value is printed for both reads and writes, with reads first. The numbers denote: ios= Number of ios performed by all groups. merge= Number of merges io the io scheduler. ticks= Number of ticks we kept the disk busy. io_queue= Total time spent in the disk queue. util= The disk utilization. A value of 100% means we kept the disk busy constantly, 50% would be a disk idling half of the time. It is also possible to get fio to dump the current output while it is running, without terminating the job. To do that, send fio the USR1 signal. You can also get regularly timed dumps by using the --status-interval parameter, or by creating a file in /tmp named fio-dump-status. If fio sees this file, it will unlink it and dump the current output status. 7.0 Terse output ---------------- For scripted usage where you typically want to generate tables or graphs of the results, fio can output the results in a semicolon separated format. The format is one long line of values, such as: 2;card0;0;0;7139336;121836;60004;1;10109;27.932460;116.933948;220;126861;3495.446807;1085.368601;226;126864;3523.635629;1089.012448;24063;99944;50.275485%;59818.274627;5540.657370;7155060;122104;60004;1;8338;29.086342;117.839068;388;128077;5032.488518;1234.785715;391;128085;5061.839412;1236.909129;23436;100928;50.287926%;59964.832030;5644.844189;14.595833%;19.394167%;123706;0;7313;0.1%;0.1%;0.1%;0.1%;0.1%;0.1%;100.0%;0.00%;0.00%;0.00%;0.00%;0.00%;0.00%;0.01%;0.02%;0.05%;0.16%;6.04%;40.40%;52.68%;0.64%;0.01%;0.00%;0.01%;0.00%;0.00%;0.00%;0.00%;0.00% A description of this job goes here. The job description (if provided) follows on a second line. To enable terse output, use the --minimal command line option. The first value is the version of the terse output format. If the output has to be changed for some reason, this number will be incremented by 1 to signify that change. Split up, the format is as follows: terse version, fio version, jobname, groupid, error READ status: Total IO (KB), bandwidth (KB/sec), IOPS, runtime (msec) Submission latency: min, max, mean, deviation (usec) Completion latency: min, max, mean, deviation (usec) Completion latency percentiles: 20 fields (see below) Total latency: min, max, mean, deviation (usec) Bw (KB/s): min, max, aggregate percentage of total, mean, deviation WRITE status: Total IO (KB), bandwidth (KB/sec), IOPS, runtime (msec) Submission latency: min, max, mean, deviation (usec) Completion latency: min, max, mean, deviation (usec) Completion latency percentiles: 20 fields (see below) Total latency: min, max, mean, deviation (usec) Bw (KB/s): min, max, aggregate percentage of total, mean, deviation CPU usage: user, system, context switches, major faults, minor faults IO depths: <=1, 2, 4, 8, 16, 32, >=64 IO latencies microseconds: <=2, 4, 10, 20, 50, 100, 250, 500, 750, 1000 IO latencies milliseconds: <=2, 4, 10, 20, 50, 100, 250, 500, 750, 1000, 2000, >=2000 Disk utilization: Disk name, Read ios, write ios, Read merges, write merges, Read ticks, write ticks, Time spent in queue, disk utilization percentage Additional Info (dependant on continue_on_error, default off): total # errors, first error code Additional Info (dependant on description being set): Text description Completion latency percentiles can be a grouping of up to 20 sets, so for the terse output fio writes all of them. Each field will look like this: 1.00%=6112 which is the Xth percentile, and the usec latency associated with it. For disk utilization, all disks used by fio are shown. So for each disk there will be a disk utilization section. 8.0 Trace file format --------------------- There are two trace file format that you can encounter. The older (v1) format is unsupported since version 1.20-rc3 (March 2008). It will still be described below in case that you get an old trace and want to understand it. In any case the trace is a simple text file with a single action per line. 8.1 Trace file format v1 ------------------------ Each line represents a single io action in the following format: rw, offset, length where rw=0/1 for read/write, and the offset and length entries being in bytes. This format is not supported in Fio versions => 1.20-rc3. 8.2 Trace file format v2 ------------------------ The second version of the trace file format was added in Fio version 1.17. It allows to access more then one file per trace and has a bigger set of possible file actions. The first line of the trace file has to be: fio version 2 iolog Following this can be lines in two different formats, which are described below. The file management format: filename action The filename is given as an absolute path. The action can be one of these: add Add the given filename to the trace open Open the file with the given filename. The filename has to have been added with the add action before. close Close the file with the given filename. The file has to have been opened before. The file io action format: filename action offset length The filename is given as an absolute path, and has to have been added and opened before it can be used with this format. The offset and length are given in bytes. The action can be one of these: wait Wait for 'offset' microseconds. Everything below 100 is discarded. read Read 'length' bytes beginning from 'offset' write Write 'length' bytes beginning from 'offset' sync fsync() the file datasync fdatasync() the file trim trim the given file from the given 'offset' for 'length' bytes 9.0 CPU idleness profiling -------------------------- In some cases, we want to understand CPU overhead in a test. For example, we test patches for the specific goodness of whether they reduce CPU usage. fio implements a balloon approach to create a thread per CPU that runs at idle priority, meaning that it only runs when nobody else needs the cpu. By measuring the amount of work completed by the thread, idleness of each CPU can be derived accordingly. An unit work is defined as touching a full page of unsigned characters. Mean and standard deviation of time to complete an unit work is reported in "unit work" section. Options can be chosen to report detailed percpu idleness or overall system idleness by aggregating percpu stats. fio-2.1.3/LICENSE000066400000000000000000000016201222032232000132620ustar00rootroot00000000000000As specified by the COPYING file, fio is free software published under version 2 of the GPL license. That covers the copying part of the license. By using fio, you are also promising to uphold the following moral obligations: - If you publish results that are done using fio, it must be clearly stated that fio was used. The specific version should also be listed. - If you develop features or bug fixes for fio, they should be sent upstream for inclusion into the main repository. This isn't specific to fio, that is a general rule for any open source project. It's just the Right Thing to do. Plus it means that you don't have to maintain the feature or change internally. In the long run, this is saving you a lot of time. I would consider the above to fall under "common courtesy", but since people tend to have differing opinions of that, it doesn't hurt to spell out my expectations clearly. fio-2.1.3/Makefile000066400000000000000000000176661222032232000137360ustar00rootroot00000000000000ifneq ($(wildcard config-host.mak),) all: include config-host.mak config-host-mak: configure @echo $@ is out-of-date, running configure @sed -n "/.*Configured with/s/[^:]*: //p" $@ | sh else config-host.mak: ifneq ($(MAKECMDGOALS),clean) @echo "Running configure for you..." @./configure endif all: include config-host.mak endif DEBUGFLAGS = -D_FORTIFY_SOURCE=2 -DFIO_INC_DEBUG CPPFLAGS= -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 $(DEBUGFLAGS) OPTFLAGS= -O3 -g -ffast-math CFLAGS = -std=gnu99 -Wwrite-strings -Wall -Wdeclaration-after-statement $(OPTFLAGS) $(EXTFLAGS) $(BUILD_CFLAGS) LIBS += -lm $(EXTLIBS) PROGS = fio SCRIPTS = tools/fio_generate_plots tools/plot/fio2gnuplot tools/genfio ifdef CONFIG_GFIO PROGS += gfio endif SOURCE := gettime.c ioengines.c init.c stat.c log.c time.c filesetup.c \ eta.c verify.c memory.c io_u.c parse.c mutex.c options.c \ lib/rbtree.c smalloc.c filehash.c profile.c debug.c lib/rand.c \ lib/num2str.c lib/ieee754.c $(wildcard crc/*.c) engines/cpu.c \ engines/mmap.c engines/sync.c engines/null.c engines/net.c \ memalign.c server.c client.c iolog.c backend.c libfio.c flow.c \ cconv.c lib/prio_tree.c json.c lib/zipf.c lib/axmap.c \ lib/lfsr.c gettime-thread.c helpers.c lib/flist_sort.c \ lib/hweight.c lib/getrusage.c idletime.c td_error.c \ profiles/tiobench.c profiles/act.c io_u_queue.c ifdef CONFIG_64BIT_LLP64 CFLAGS += -DBITS_PER_LONG=32 endif ifdef CONFIG_64BIT CFLAGS += -DBITS_PER_LONG=64 endif ifdef CONFIG_32BIT CFLAGS += -DBITS_PER_LONG=32 endif ifdef CONFIG_LIBAIO SOURCE += engines/libaio.c endif ifdef CONFIG_RDMA SOURCE += engines/rdma.c endif ifdef CONFIG_POSIXAIO SOURCE += engines/posixaio.c endif ifdef CONFIG_LINUX_FALLOCATE SOURCE += engines/falloc.c endif ifdef CONFIG_LINUX_EXT4_MOVE_EXTENT SOURCE += engines/e4defrag.c endif ifdef CONFIG_LINUX_SPLICE SOURCE += engines/splice.c endif ifdef CONFIG_GUASI SOURCE += engines/guasi.c endif ifdef CONFIG_FUSION_AW SOURCE += engines/fusion-aw.c endif ifdef CONFIG_SOLARISAIO SOURCE += engines/solarisaio.c endif ifdef CONFIG_WINDOWSAIO SOURCE += engines/windowsaio.c endif ifndef CONFIG_STRSEP SOURCE += lib/strsep.c endif ifndef CONFIG_STRCASESTR SOURCE += lib/strcasestr.c endif ifndef CONFIG_GETOPT_LONG_ONLY SOURCE += lib/getopt_long.c endif ifndef CONFIG_INET_ATON SOURCE += lib/inet_aton.c endif ifeq ($(CONFIG_TARGET_OS), Linux) SOURCE += diskutil.c fifo.c blktrace.c cgroup.c trim.c engines/sg.c \ engines/binject.c LIBS += -lpthread -ldl LDFLAGS += -rdynamic endif ifeq ($(CONFIG_TARGET_OS), Android) SOURCE += diskutil.c fifo.c blktrace.c trim.c profiles/tiobench.c LIBS += -ldl LDFLAGS += -rdynamic endif ifeq ($(CONFIG_TARGET_OS), SunOS) LIBS += -lpthread -ldl CPPFLAGS += -D__EXTENSIONS__ endif ifeq ($(CONFIG_TARGET_OS), FreeBSD) LIBS += -lpthread -lrt LDFLAGS += -rdynamic endif ifeq ($(CONFIG_TARGET_OS), NetBSD) LIBS += -lpthread -lrt LDFLAGS += -rdynamic endif ifeq ($(CONFIG_TARGET_OS), AIX) LIBS += -lpthread -ldl -lrt CPPFLAGS += -D_LARGE_FILES -D__ppc__ LDFLAGS += -L/opt/freeware/lib -Wl,-blibpath:/opt/freeware/lib:/usr/lib:/lib -Wl,-bmaxdata:0x80000000 endif ifeq ($(CONFIG_TARGET_OS), HP-UX) LIBS += -lpthread -ldl -lrt CFLAGS += -D_LARGEFILE64_SOURCE -D_XOPEN_SOURCE_EXTENDED endif ifeq ($(CONFIG_TARGET_OS), Darwin) LIBS += -lpthread -ldl endif ifneq (,$(findstring CYGWIN,$(CONFIG_TARGET_OS))) SOURCE := $(filter-out engines/mmap.c,$(SOURCE)) SOURCE += os/windows/posix.c LIBS += -lpthread -lpsapi -lws2_32 CFLAGS += -DPSAPI_VERSION=1 -Ios/windows/posix/include -Wno-format endif OBJS = $(SOURCE:.c=.o) FIO_OBJS = $(OBJS) fio.o GFIO_OBJS = $(OBJS) gfio.o graph.o tickmarks.o ghelpers.o goptions.o gerror.o \ gclient.o gcompat.o cairo_text_helpers.o printing.o -include $(OBJS:.o=.d) T_SMALLOC_OBJS = t/stest.o T_SMALLOC_OBJS += gettime.o mutex.o smalloc.o t/log.o T_SMALLOC_PROGS = t/stest T_IEEE_OBJS = t/ieee754.o T_IEEE_OBJS += lib/ieee754.o T_IEEE_PROGS = t/ieee754 T_ZIPF_OBS = t/genzipf.o T_ZIPF_OBJS += t/log.o lib/ieee754.o lib/rand.o lib/zipf.o t/genzipf.o T_ZIPF_PROGS = t/genzipf T_AXMAP_OBJS = t/axmap.o T_AXMAP_OBJS += lib/lfsr.o lib/axmap.o T_AXMAP_PROGS = t/axmap T_LFSR_TEST_OBJS = t/lfsr-test.o T_LFSR_TEST_OBJS += lib/lfsr.o T_LFSR_TEST_PROGS = t/lfsr-test T_OBJS = $(T_SMALLOC_OBJS) T_OBJS += $(T_IEEE_OBJS) T_OBJS += $(T_ZIPF_OBJS) T_OBJS += $(T_AXMAP_OBJS) T_OBJS += $(T_LFSR_TEST_OBJS) T_PROGS = $(T_SMALLOC_PROGS) T_PROGS += $(T_IEEE_PROGS) T_PROGS += $(T_ZIPF_PROGS) T_PROGS += $(T_AXMAP_PROGS) T_PROGS += $(T_LFSR_TEST_PROGS) ifneq ($(findstring $(MAKEFLAGS),s),s) ifndef V QUIET_CC = @echo ' ' CC $@; QUIET_LINK = @echo ' ' LINK $@; QUIET_DEP = @echo ' ' DEP $@; endif endif ifeq ($(CONFIG_TARGET_OS), SunOS) INSTALL = ginstall else INSTALL = install endif prefix = /usr/local bindir = $(prefix)/bin ifeq ($(CONFIG_TARGET_OS), Darwin) mandir = /usr/share/man sharedir = /usr/share/fio else mandir = $(prefix)/man sharedir = $(prefix)/share/fio endif all: $(PROGS) $(SCRIPTS) FORCE .PHONY: all install clean .PHONY: FORCE cscope FIO-VERSION-FILE: FORCE @$(SHELL) ./FIO-VERSION-GEN -include FIO-VERSION-FILE override CFLAGS += -DFIO_VERSION='"$(FIO_VERSION)"' .c.o: FORCE FIO-VERSION-FILE $(QUIET_CC)$(CC) -o $@ $(CFLAGS) $(CPPFLAGS) -c $< @$(CC) -MM $(CFLAGS) $(CPPFLAGS) $*.c > $*.d @mv -f $*.d $*.d.tmp @sed -e 's|.*:|$*.o:|' < $*.d.tmp > $*.d @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -1 | \ sed -e 's/^ *//' -e 's/$$/:/' >> $*.d @rm -f $*.d.tmp init.o: FIO-VERSION-FILE init.c $(QUIET_CC)$(CC) -o init.o $(CFLAGS) $(CPPFLAGS) -c init.c gcompat.o: gcompat.c gcompat.h $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gcompat.c goptions.o: goptions.c goptions.h $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c goptions.c ghelpers.o: ghelpers.c ghelpers.h $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c ghelpers.c gerror.o: gerror.c gerror.h $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gerror.c gclient.o: gclient.c gclient.h $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gclient.c gfio.o: gfio.c ghelpers.c $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gfio.c graph.o: graph.c graph.h $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c graph.c cairo_text_helpers.o: cairo_text_helpers.c cairo_text_helpers.h $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c cairo_text_helpers.c printing.o: printing.c printing.h $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c printing.c t/stest: $(T_SMALLOC_OBJS) $(QUIET_LINK)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_SMALLOC_OBJS) $(LIBS) t/ieee754: $(T_IEEE_OBJS) $(QUIET_LINK)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_IEEE_OBJS) $(LIBS) fio: $(FIO_OBJS) $(QUIET_LINK)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(FIO_OBJS) $(LIBS) gfio: $(GFIO_OBJS) $(QUIET_LINK)$(CC) $(LDFLAGS) -o gfio $(GFIO_OBJS) $(LIBS) $(GTK_LDFLAGS) t/genzipf: $(T_ZIPF_OBJS) $(QUIET_LINK)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_ZIPF_OBJS) $(LIBS) t/axmap: $(T_AXMAP_OBJS) $(QUIET_LINK)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_AXMAP_OBJS) $(LIBS) t/lfsr-test: $(T_LFSR_TEST_OBJS) $(QUIET_LINK)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_LFSR_TEST_OBJS) $(LIBS) clean: FORCE -rm -f .depend $(FIO_OBJS) $(GFIO_OBJS) $(OBJS) $(T_OBJS) $(PROGS) $(T_PROGS) core.* core gfio FIO-VERSION-FILE *.d config-host.mak config-host.h distclean: clean FORCE @rm -f cscope.out cscope: @cscope -b -R tools/plot/fio2gnuplot.1: @cat tools/plot/fio2gnuplot.manpage | txt2man -t fio2gnuplot > tools/plot/fio2gnuplot.1 install: $(PROGS) $(SCRIPTS) tools/plot/fio2gnuplot.1 FORCE $(INSTALL) -m 755 -d $(DESTDIR)$(bindir) $(INSTALL) $(PROGS) $(SCRIPTS) $(DESTDIR)$(bindir) $(INSTALL) -m 755 -d $(DESTDIR)$(mandir)/man1 $(INSTALL) -m 644 fio.1 $(DESTDIR)$(mandir)/man1 $(INSTALL) -m 644 tools/fio_generate_plots.1 $(DESTDIR)$(mandir)/man1 $(INSTALL) -m 644 tools/plot/fio2gnuplot.1 $(DESTDIR)$(mandir)/man1 $(INSTALL) -m 755 -d $(DESTDIR)$(sharedir) $(INSTALL) -m 644 tools/plot/*gpm $(DESTDIR)$(sharedir)/ fio-2.1.3/README000066400000000000000000000311571222032232000131450ustar00rootroot00000000000000fio --- fio is a tool that will spawn a number of threads or processes doing a particular type of io action as specified by the user. fio takes a number of global parameters, each inherited by the thread unless otherwise parameters given to them overriding that setting is given. The typical use of fio is to write a job file matching the io load one wants to simulate. Source ------ fio resides in a git repo, the canonical place is: git://git.kernel.dk/fio.git When inside a corporate firewall, git:// URL sometimes does not work. If git:// does not work, use the http protocol instead: http://git.kernel.dk/fio.git Snapshots are frequently generated and include the git meta data as well. Snapshots can download from: http://brick.kernel.dk/snaps/ Binary packages --------------- Debian: Starting with Debian "Squeeze", fio packages are part of the official Debian repository. http://packages.debian.org/search?keywords=fio Ubuntu: Starting with Ubuntu 10.04 LTS (aka "Lucid Lynx"), fio packages are part of the Ubuntu "universe" repository. http://packages.ubuntu.com/search?keywords=fio Red Hat, CentOS & Co: Dag Wieërs has RPMs for Red Hat related distros, find them here: http://dag.wieers.com/rpm/packages/fio/ Mandriva: Mandriva has integrated fio into their package repository, so installing on that distro should be as easy as typing 'urpmi fio'. Solaris: Packages for Solaris are available from OpenCSW. Install their pkgutil tool (http://www.opencsw.org/get-it/pkgutil/) and then install fio via 'pkgutil -i fio'. Windows: Bruce Cran has fio packages for Windows at http://www.bluestop.org/fio/ . Mailing list ------------ The fio project mailing list is meant for anything related to fio including general discussion, bug reporting, questions, and development. An automated mail detailing recent commits is automatically sent to the list at most daily. The list address is fio@vger.kernel.org, subscribe by sending an email to majordomo@vger.kernel.org with subscribe fio in the body of the email. Archives can be found here: http://www.spinics.net/lists/fio/ and archives for the old list can be found here: http://maillist.kernel.dk/fio-devel/ Building -------- Just type 'configure', 'make' and 'make install'. Note that GNU make is required. On BSD it's available from devel/gmake; on Solaris it's in the SUNWgmake package. On platforms where GNU make isn't the default, type 'gmake' instead of 'make'. Configure will print the enabled options. Note that on Linux based platforms, the libaio development packages must be installed to use the libaio engine. Depending on distro, it is usually called libaio-devel or libaio-dev. For gfio, gtk 2.18 (or newer), associated glib threads, and cairo are required to be installed. gfio isn't built automatically and can be enabled with a --enable-gfio option to configure. To build FIO with a cross-compiler: $ make clean $ make CROSS_COMPILE=/path/to/toolchain/prefix Configure will attempt to determine the target platform automatically. Windows ------- On Windows Cygwin (http://www.cygwin.com/) is required in order to build fio. To create an MSI installer package install WiX 3.7 from http://wixtoolset.org and run dobuild.cmd from the os/windows directory. How to compile FIO on 64-bit Windows: 1. Install Cygwin (http://www.cygwin.com/setup.exe). Install 'make' and all packages starting with 'mingw64-i686' and 'mingw64-x86_64'. 2. Download ftp://sourceware.org/pub/pthreads-win32/prebuilt-dll-2-9-1-release/dll/x64/pthreadGC2.dll and copy to the fio source directory. 3. Open the Cygwin Terminal. 4. Go to the fio directory (source files). 5. Run 'make clean'. 6. Run 'make'. To build fio on 32-bit Windows, download x86/pthreadGC2.dll instead and do './configure --build-32bit-win=yes' before 'make'. It's recommended that once built or installed, fio be run in a Command Prompt or other 'native' console such as console2, since there are known to be display and signal issues when running it under a Cygwin shell (see http://code.google.com/p/mintty/issues/detail?id=56 for details). Command line ------------ $ fio --debug Enable some debugging options (see below) --parse-only Parse options only, don't start any IO --output Write output to file --runtime Runtime in seconds --latency-log Generate per-job latency logs --bandwidth-log Generate per-job bandwidth logs --minimal Minimal (terse) output --output-format=type Output format (terse,json,normal) --terse-version=type Terse version output format (default 3, or 2 or 4). --version Print version info and exit --help Print this page --cpuclock-test Perform test/validation of CPU clock --cmdhelp=cmd Print command help, "all" for all of them --enghelp=engine Print ioengine help, or list available ioengines --enghelp=engine,cmd Print help for an ioengine cmd --showcmd Turn a job file into command line options --readonly Turn on safety read-only checks, preventing writes --eta=when When ETA estimate should be printed May be "always", "never" or "auto" --eta-newline=time Force a new line for every 'time' period passed --status-interval=t Force full status dump every 't' period passed --section=name Only run specified section in job file. Multiple sections can be specified. --alloc-size=kb Set smalloc pool to this size in kb (def 1024) --warnings-fatal Fio parser warnings are fatal --max-jobs Maximum number of threads/processes to support --server=args Start backend server. See Client/Server section. --client=host Connect to specified backend. --idle-prof=option Report cpu idleness on a system or percpu basis (option=system,percpu) or run unit work calibration only (option=calibrate). Any parameters following the options will be assumed to be job files, unless they match a job file parameter. Multiple job files can be listed and each job file will be regarded as a separate group. fio will stonewall execution between each group. The --readonly option is an extra safety guard to prevent users from accidentally starting a write workload when that is not desired. Fio will only write if rw=write/randwrite/rw/randrw is given. This extra safety net can be used as an extra precaution as --readonly will also enable a write check in the io engine core to prevent writes due to unknown user space bug(s). The --debug option triggers additional logging by fio. Currently, additional logging is available for: process Dump info related to processes file Dump info related to file actions io Dump info related to IO queuing mem Dump info related to memory allocations blktrace Dump info related to blktrace setup verify Dump info related to IO verification all Enable all debug options random Dump info related to random offset generation parse Dump info related to option matching and parsing diskutil Dump info related to disk utilization updates job:x Dump info only related to job number x mutex Dump info only related to mutex up/down ops profile Dump info related to profile extensions time Dump info related to internal time keeping ? or help Show available debug options. One can specify multiple debug options: e.g. --debug=file,mem will enable file and memory debugging. The --section option allows one to combine related jobs into one file. E.g. one job file could define light, moderate, and heavy sections. Tell fio to run only the "heavy" section by giving --section=heavy command line option. One can also specify the "write" operations in one section and "verify" operation in another section. The --section option only applies to job sections. The reserved 'global' section is always parsed and used. The --alloc-size switch allows one to use a larger pool size for smalloc. If running large jobs with randommap enabled, fio can run out of memory. Smalloc is an internal allocator for shared structures from a fixed size memory pool. The pool size defaults to 1024k and can grow to 128 pools. NOTE: While running .fio_smalloc.* backing store files are visible in /tmp. Job file -------- See the HOWTO file for a complete description of job file syntax and parameters. The --cmdhelp option also lists all options. If used with an option argument, --cmdhelp will detail the given option. The job file format is in the ini style format, as that is easy for the user to review and modify. This README contains the terse version. Job files can describe big and complex setups that are not possible with the command line. Job files are a good practice even for simple jobs since the file provides an easily accessed record of the workload and can include comments. See the examples/ directory for inspiration on how to write job files. Note the copyright and license requirements currently apply to examples/ files. Client/server ------------ Normally fio is invoked as a stand-alone application on the machine where the IO workload should be generated. However, the frontend and backend of fio can be run separately. Ie the fio server can generate an IO workload on the "Device Under Test" while being controlled from another machine. Start the server on the machine which has access to the storage DUT: fio --server=args where args defines what fio listens to. The arguments are of the form 'type,hostname or IP,port'. 'type' is either 'ip' (or ip4) for TCP/IP v4, 'ip6' for TCP/IP v6, or 'sock' for a local unix domain socket. 'hostname' is either a hostname or IP address, and 'port' is the port to listen to (only valid for TCP/IP, not a local socket). Some examples: 1) fio --server Start a fio server, listening on all interfaces on the default port (8765). 2) fio --server=ip:hostname,4444 Start a fio server, listening on IP belonging to hostname and on port 4444. 3) fio --server=ip6:::1,4444 Start a fio server, listening on IPv6 localhost ::1 and on port 4444. 4) fio --server=,4444 Start a fio server, listening on all interfaces on port 4444. 5) fio --server=1.2.3.4 Start a fio server, listening on IP 1.2.3.4 on the default port. 6) fio --server=sock:/tmp/fio.sock Start a fio server, listening on the local socket /tmp/fio.sock. Once a server is running, a "client" can connect to the fio server with: fio --local-args --client= --remote-args where --local-args are arguments for the client where it is running, 'server' is the connect string, and --remote-args and are sent to the server. The 'server' string follows the same format as it does on the server side, to allow IP/hostname/socket and port strings. Fio can connect to multiple servers this way: fio --client= --client= Platforms --------- Fio works on (at least) Linux, Solaris, AIX, HP-UX, OSX, NetBSD, Windows and FreeBSD. Some features and/or options may only be available on some of the platforms, typically because those features only apply to that platform (like the solarisaio engine, or the splice engine on Linux). Some features are not available on FreeBSD/Solaris even if they could be implemented, I'd be happy to take patches for that. An example of that is disk utility statistics and (I think) huge page support, support for that does exist in FreeBSD/Solaris. Fio uses pthread mutexes for signalling and locking and FreeBSD does not support process shared pthread mutexes. As a result, only threads are supported on FreeBSD. This could be fixed with sysv ipc locking or other locking alternatives. Other *BSD platforms are untested, but fio should work there almost out of the box. Since I don't do test runs or even compiles on those platforms, your mileage may vary. Sending me patches for other platforms is greatly appreciated. There's a lot of value in having the same test/benchmark tool available on all platforms. Note that POSIX aio is not enabled by default on AIX. Messages like these: Symbol resolution failed for /usr/lib/libc.a(posix_aio.o) because: Symbol _posix_kaio_rdwr (number 2) is not exported from dependent module /unix. indicate one needs to enable POSIX aio. Run the following commands as root: # lsdev -C -l posix_aio0 posix_aio0 Defined Posix Asynchronous I/O # cfgmgr -l posix_aio0 # lsdev -C -l posix_aio0 posix_aio0 Available Posix Asynchronous I/O POSIX aio should work now. To make the change permanent: # chdev -l posix_aio0 -P -a autoconfig='available' posix_aio0 changed Author ------ Fio was written by Jens Axboe to enable flexible testing of the Linux IO subsystem and schedulers. He got tired of writing specific test applications to simulate a given workload, and found that the existing io benchmark/test tools out there weren't flexible enough to do what he wanted. Jens Axboe 20060905 fio-2.1.3/REPORTING-BUGS000066400000000000000000000007651222032232000143200ustar00rootroot00000000000000Reporting a bug --------------- If you notice anything that seems like a fio bug, please do send email to the list (fio@vger.kernel.org, see README) about it. You'll need to report at least: 1) A description of what you think the bug is 2) Environment (Linux distro version, kernel version). This is mostly needed if it's a build bug. 3) The output from fio --version. 4) How to reproduce. Please include a full list of the parameters passed to fio and the job file used (if any). That's it! fio-2.1.3/SERVER-TODO000066400000000000000000000002021222032232000137440ustar00rootroot00000000000000- Collate ETA output from multiple connections into 1 - If group_reporting is set, collate final output from multiple connections fio-2.1.3/arch/000077500000000000000000000000001222032232000131735ustar00rootroot00000000000000fio-2.1.3/arch/arch-alpha.h000066400000000000000000000007751222032232000153550ustar00rootroot00000000000000#ifndef ARCH_ALPHA_H #define ARCH_ALPHA_H #define FIO_ARCH (arch_alpha) #ifndef __NR_ioprio_set #define __NR_ioprio_set 442 #define __NR_ioprio_get 443 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 413 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 468 #define __NR_sys_tee 470 #define __NR_sys_vmsplice 471 #endif #define nop do { } while (0) #define read_barrier() __asm__ __volatile__("mb": : :"memory") #define write_barrier() __asm__ __volatile__("wmb": : :"memory") #endif fio-2.1.3/arch/arch-arm.h000066400000000000000000000017551222032232000150460ustar00rootroot00000000000000#ifndef ARCH_ARM_H #define ARCH_ARM_H #define FIO_ARCH (arch_arm) #ifndef __NR_ioprio_set #define __NR_ioprio_set 314 #define __NR_ioprio_get 315 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 270 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 340 #define __NR_sys_tee 342 #define __NR_sys_vmsplice 343 #endif #if defined (__ARM_ARCH_4__) || defined (__ARM_ARCH_4T__) \ || defined (__ARM_ARCH_5__) || defined (__ARM_ARCH_5T__) || defined (__ARM_ARCH_5TE__) || defined (__ARM_ARCH_5TEJ__) \ || defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) #define nop __asm__ __volatile__("mov\tr0,r0\t@ nop\n\t") #define read_barrier() __asm__ __volatile__ ("" : : : "memory") #define write_barrier() __asm__ __volatile__ ("" : : : "memory") #elif defined(__ARM_ARCH_7A__) #define nop __asm__ __volatile__ ("nop") #define read_barrier() __sync_synchronize() #define write_barrier() __sync_synchronize() #endif #endif fio-2.1.3/arch/arch-generic.h000066400000000000000000000003641222032232000156760ustar00rootroot00000000000000#ifndef ARCH_GENERIC_H #define ARCH_GENERIC_H #define FIO_ARCH (arch_generic) #define nop do { } while (0) #define read_barrier() __asm__ __volatile__("": : :"memory") #define write_barrier() __asm__ __volatile__("": : :"memory") #endif fio-2.1.3/arch/arch-hppa.h000066400000000000000000000007701222032232000152130ustar00rootroot00000000000000#ifndef ARCH_HPPA_H #define ARCH_HPPA_H #define FIO_ARCH (arch_hppa) #ifndef __NR_ioprio_set #define __NR_ioprio_set 267 #define __NR_ioprio_get 268 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 236 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 291 #define __NR_sys_tee 293 #define __NR_sys_vmsplice 294 #endif #define nop do { } while (0) #define read_barrier() __asm__ __volatile__ ("" : : : "memory") #define write_barrier() __asm__ __volatile__ ("" : : : "memory") #endif fio-2.1.3/arch/arch-ia64.h000066400000000000000000000021651222032232000150260ustar00rootroot00000000000000#ifndef ARCH_IA64_H #define ARCH_IA64_H #define FIO_ARCH (arch_ia64) #ifndef __NR_ioprio_set #define __NR_ioprio_set 1274 #define __NR_ioprio_get 1275 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 1234 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 1297 #define __NR_sys_tee 1301 #define __NR_sys_vmsplice 1302 #endif #define nop asm volatile ("hint @pause" ::: "memory"); #define read_barrier() asm volatile ("mf" ::: "memory") #define write_barrier() asm volatile ("mf" ::: "memory") #define ia64_popcnt(x) \ ({ \ unsigned long ia64_intri_res; \ asm ("popcnt %0=%1" : "=r" (ia64_intri_res) : "r" (x)); \ ia64_intri_res; \ }) static inline unsigned long arch_ffz(unsigned long bitmask) { return ia64_popcnt(bitmask & (~bitmask - 1)); } static inline unsigned long long get_cpu_clock(void) { unsigned long long ret; __asm__ __volatile__("mov %0=ar.itc" : "=r" (ret) : : "memory"); return ret; } #define ARCH_HAVE_INIT extern int tsc_reliable; static inline int arch_init(char *envp[]) { tsc_reliable = 1; return 0; } #define ARCH_HAVE_FFZ #define ARCH_HAVE_CPU_CLOCK #endif fio-2.1.3/arch/arch-mips.h000066400000000000000000000010161222032232000152250ustar00rootroot00000000000000#ifndef ARCH_MIPS64_H #define ARCH_MIPS64_H #define FIO_ARCH (arch_mips) #ifndef __NR_ioprio_set #define __NR_ioprio_set 314 #define __NR_ioprio_get 315 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 215 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 263 #define __NR_sys_tee 265 #define __NR_sys_vmsplice 266 #endif #define read_barrier() __asm__ __volatile__("": : :"memory") #define write_barrier() __asm__ __volatile__("": : :"memory") #define nop __asm__ __volatile__("": : :"memory") #endif fio-2.1.3/arch/arch-ppc.h000066400000000000000000000045771222032232000150560ustar00rootroot00000000000000#ifndef ARCH_PPC_H #define ARCH_PPH_H #include #include #include #include #define FIO_ARCH (arch_ppc) #ifndef __NR_ioprio_set #define __NR_ioprio_set 273 #define __NR_ioprio_get 274 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 233 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 283 #define __NR_sys_tee 284 #define __NR_sys_vmsplice 285 #endif #define nop do { } while (0) #ifdef __powerpc64__ #define read_barrier() __asm__ __volatile__ ("lwsync" : : : "memory") #else #define read_barrier() __asm__ __volatile__ ("sync" : : : "memory") #endif #define write_barrier() __asm__ __volatile__ ("sync" : : : "memory") static inline int __ilog2(unsigned long bitmask) { int lz; asm ("cntlzw %0,%1" : "=r" (lz) : "r" (bitmask)); return 31 - lz; } static inline int arch_ffz(unsigned long bitmask) { if ((bitmask = ~bitmask) == 0) return 32; return __ilog2(bitmask & -bitmask); } static inline unsigned int mfspr(unsigned int reg) { unsigned int val; asm volatile("mfspr %0,%1": "=r" (val) : "K" (reg)); return val; } #define SPRN_TBRL 0x10C /* Time Base Register Lower */ #define SPRN_TBRU 0x10D /* Time Base Register Upper */ #define SPRN_ATBL 0x20E /* Alternate Time Base Lower */ #define SPRN_ATBU 0x20F /* Alternate Time Base Upper */ static inline unsigned long long get_cpu_clock(void) { unsigned int tbl, tbu0, tbu1; unsigned long long ret; do { if (arch_flags & ARCH_FLAG_1) { tbu0 = mfspr(SPRN_ATBU); tbl = mfspr(SPRN_ATBL); tbu1 = mfspr(SPRN_ATBU); } else { tbu0 = mfspr(SPRN_TBRU); tbl = mfspr(SPRN_TBRL); tbu1 = mfspr(SPRN_TBRU); } } while (tbu0 != tbu1); ret = (((unsigned long long)tbu0) << 32) | tbl; return ret; } static void atb_child(void) { arch_flags |= ARCH_FLAG_1; get_cpu_clock(); _exit(0); } static void atb_clocktest(void) { pid_t pid; pid = fork(); if (!pid) atb_child(); else if (pid != -1) { int status; pid = wait(&status); if (pid == -1 || !WIFEXITED(status)) arch_flags &= ~ARCH_FLAG_1; else arch_flags |= ARCH_FLAG_1; } } #define ARCH_HAVE_INIT extern int tsc_reliable; static inline int arch_init(char *envp[]) { tsc_reliable = 1; atb_clocktest(); return 0; } #define ARCH_HAVE_FFZ /* * We don't have it on all platforms, lets comment this out until we * can handle it more intelligently. * * #define ARCH_HAVE_CPU_CLOCK */ #endif fio-2.1.3/arch/arch-s390.h000066400000000000000000000015041222032232000147550ustar00rootroot00000000000000#ifndef ARCH_S390_H #define ARCH_S390_H #define FIO_ARCH (arch_s390) #ifndef __NR_ioprio_set #define __NR_ioprio_set 282 #define __NR_ioprio_get 283 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 253 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 306 #define __NR_sys_tee 308 #define __NR_sys_vmsplice 309 #endif #define nop asm volatile ("diag 0,0,68" : : : "memory") #define read_barrier() asm volatile("bcr 15,0" : : : "memory") #define write_barrier() asm volatile("bcr 15,0" : : : "memory") static inline unsigned long long get_cpu_clock(void) { unsigned long long clk; __asm__ __volatile__("stck %0" : "=Q" (clk) : : "cc"); return clk; } #define ARCH_HAVE_INIT extern int tsc_reliable; static inline int arch_init(char *envp[]) { tsc_reliable = 1; return 0; } #define ARCH_HAVE_CPU_CLOCK #endif fio-2.1.3/arch/arch-sh.h000066400000000000000000000021501222032232000146670ustar00rootroot00000000000000/* Renesas SH (32bit) only */ #ifndef ARCH_SH_H #define ARCH_SH_H #define FIO_ARCH (arch_sh) #ifndef __NR_ioprio_set #define __NR_ioprio_set 288 #define __NR_ioprio_get 289 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 250 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 313 #define __NR_sys_tee 315 #define __NR_sys_vmsplice 316 #endif #define nop __asm__ __volatile__ ("nop": : :"memory") #define mb() \ do { \ if (arch_flags & ARCH_FLAG_1) \ __asm__ __volatile__ ("synco": : :"memory"); \ else \ __asm__ __volatile__ (" " : : : "memory"); \ } while (0) #define read_barrier() mb() #define write_barrier() mb() #include #include extern unsigned long arch_flags; #define CPU_HAS_LLSC 0x0040 static inline int arch_init(char *envp[]) { Elf32_auxv_t *auxv; while (*envp++ != NULL) ; for (auxv = (Elf32_auxv_t *) envp; auxv->a_type != AT_NULL; auxv++) { if (auxv->a_type == AT_HWCAP) { if (auxv->a_un.a_val & CPU_HAS_LLSC) { arch_flags |= ARCH_FLAG_1; break; } } } return 0; } #define ARCH_HAVE_INIT #endif fio-2.1.3/arch/arch-sparc.h000066400000000000000000000007721222032232000153750ustar00rootroot00000000000000#ifndef ARCH_SPARC_H #define ARCH_SPARC_H #define FIO_ARCH (arch_sparc) #ifndef __NR_ioprio_set #define __NR_ioprio_set 196 #define __NR_ioprio_get 218 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 209 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 232 #define __NR_sys_tee 280 #define __NR_sys_vmsplice 25 #endif #define nop do { } while (0) #define read_barrier() __asm__ __volatile__ ("" : : : "memory") #define write_barrier() __asm__ __volatile__ ("" : : : "memory") #endif fio-2.1.3/arch/arch-sparc64.h000066400000000000000000000012141222032232000155370ustar00rootroot00000000000000#ifndef ARCH_SPARC64_H #define ARCH_SPARC64_H #define FIO_ARCH (arch_sparc64) #ifndef __NR_ioprio_set #define __NR_ioprio_set 196 #define __NR_ioprio_get 218 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 209 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 232 #define __NR_sys_tee 280 #define __NR_sys_vmsplice 25 #endif #define nop do { } while (0) #define membar_safe(type) \ do { __asm__ __volatile__("ba,pt %%xcc, 1f\n\t" \ " membar " type "\n" \ "1:\n" \ : : : "memory"); \ } while (0) #define read_barrier() membar_safe("#LoadLoad") #define write_barrier() membar_safe("#StoreStore") #endif fio-2.1.3/arch/arch-x86-common.h000066400000000000000000000024141222032232000161730ustar00rootroot00000000000000#ifndef FIO_ARCH_X86_COMMON #define FIO_ARCH_X86_COMMON #include static inline void cpuid(unsigned int op, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx) { *eax = op; *ecx = 0; do_cpuid(eax, ebx, ecx, edx); } #define ARCH_HAVE_INIT extern int tsc_reliable; static inline int arch_init_intel(unsigned int level) { unsigned int eax, ebx, ecx = 0, edx; /* * Check for TSC */ eax = 1; do_cpuid(&eax, &ebx, &ecx, &edx); if (!(edx & (1U << 4))) return 0; /* * Check for constant rate and synced (across cores) TSC */ eax = 0x80000007; do_cpuid(&eax, &ebx, &ecx, &edx); return edx & (1U << 8); } static inline int arch_init_amd(unsigned int level) { unsigned int eax, ebx, ecx, edx; cpuid(0x80000000, &eax, &ebx, &ecx, &edx); if (eax < 0x80000007) return 0; cpuid(0x80000007, &eax, &ebx, &ecx, &edx); if (edx & (1 << 8)) return 1; return 0; } static inline int arch_init(char *envp[]) { unsigned int level; char str[12]; cpuid(0, &level, (unsigned int *) &str[0], (unsigned int *) &str[8], (unsigned int *) &str[4]); if (!strcmp(str, "GenuineIntel")) tsc_reliable = arch_init_intel(level); else if (!strcmp(str, "AuthenticAMD")) tsc_reliable = arch_init_amd(level); return 0; } #endif fio-2.1.3/arch/arch-x86.h000066400000000000000000000022261222032232000147060ustar00rootroot00000000000000#ifndef ARCH_X86_H #define ARCH_X86_H static inline void do_cpuid(unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx) { asm volatile("xchgl %%ebx, %1\ncpuid\nxchgl %%ebx, %1" : "=a" (*eax), "=r" (*ebx), "=c" (*ecx), "=d" (*edx) : "0" (*eax) : "memory"); } #include "arch-x86-common.h" #define FIO_ARCH (arch_i386) #ifndef __NR_ioprio_set #define __NR_ioprio_set 289 #define __NR_ioprio_get 290 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 250 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 313 #define __NR_sys_tee 315 #define __NR_sys_vmsplice 316 #endif #define FIO_HUGE_PAGE 4194304 #define nop __asm__ __volatile__("rep;nop": : :"memory") #define read_barrier() __asm__ __volatile__("": : :"memory") #define write_barrier() __asm__ __volatile__("": : :"memory") static inline unsigned long arch_ffz(unsigned long bitmask) { __asm__("bsfl %1,%0" :"=r" (bitmask) :"r" (~bitmask)); return bitmask; } static inline unsigned long long get_cpu_clock(void) { unsigned long long ret; __asm__ __volatile__("rdtsc" : "=A" (ret)); return ret; } #define ARCH_HAVE_FFZ #define ARCH_HAVE_CPU_CLOCK #endif fio-2.1.3/arch/arch-x86_64.h000066400000000000000000000025211222032232000152150ustar00rootroot00000000000000#ifndef ARCH_X86_64_h #define ARCH_X86_64_h static inline void do_cpuid(unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx) { asm volatile("cpuid" : "=a" (*eax), "=b" (*ebx), "=r" (*ecx), "=d" (*edx) : "0" (*eax), "2" (*ecx) : "memory"); } #include "arch-x86-common.h" #define FIO_ARCH (arch_x86_64) #ifndef __NR_ioprio_set #define __NR_ioprio_set 251 #define __NR_ioprio_get 252 #endif #ifndef __NR_fadvise64 #define __NR_fadvise64 221 #endif #ifndef __NR_sys_splice #define __NR_sys_splice 275 #define __NR_sys_tee 276 #define __NR_sys_vmsplice 278 #endif #ifndef __NR_shmget #define __NR_shmget 29 #define __NR_shmat 30 #define __NR_shmctl 31 #define __NR_shmdt 67 #endif #define FIO_HUGE_PAGE 2097152 #define nop __asm__ __volatile__("rep;nop": : :"memory") #define read_barrier() __asm__ __volatile__("lfence":::"memory") #define write_barrier() __asm__ __volatile__("sfence":::"memory") static inline unsigned long arch_ffz(unsigned long bitmask) { __asm__("bsf %1,%0" :"=r" (bitmask) :"r" (~bitmask)); return bitmask; } static inline unsigned long long get_cpu_clock(void) { unsigned int lo, hi; __asm__ __volatile__("rdtsc" : "=a" (lo), "=d" (hi)); return ((unsigned long long) hi << 32ULL) | lo; } #define ARCH_HAVE_FFZ #define ARCH_HAVE_SSE4_2 #define ARCH_HAVE_CPU_CLOCK #endif fio-2.1.3/arch/arch.h000066400000000000000000000024461222032232000142670ustar00rootroot00000000000000#ifndef ARCH_H #define ARCH_H enum { arch_x86_64 = 1, arch_i386, arch_ppc, arch_ia64, arch_s390, arch_alpha, arch_sparc, arch_sparc64, arch_arm, arch_sh, arch_hppa, arch_mips, arch_generic, arch_nr, }; enum { ARCH_FLAG_1 = 1 << 0, ARCH_FLAG_2 = 1 << 1, ARCH_FLAG_3 = 1 << 2, ARCH_FLAG_4 = 1 << 3, }; extern unsigned long arch_flags; #if defined(__i386__) #include "arch-x86.h" #elif defined(__x86_64__) #include "arch-x86_64.h" #elif defined(__powerpc__) || defined(__powerpc64__) || defined(__ppc__) #include "arch-ppc.h" #elif defined(__ia64__) #include "arch-ia64.h" #elif defined(__alpha__) #include "arch-alpha.h" #elif defined(__s390x__) || defined(__s390__) #include "arch-s390.h" #elif defined(__sparc__) #include "arch-sparc.h" #elif defined(__sparc64__) #include "arch-sparc64.h" #elif defined(__arm__) #include "arch-arm.h" #elif defined(__mips__) || defined(__mips64__) #include "arch-mips.h" #elif defined(__sh__) #include "arch-sh.h" #elif defined(__hppa__) #include "arch-hppa.h" #else #warning "Unknown architecture, attempting to use generic model." #include "arch-generic.h" #endif #ifdef ARCH_HAVE_FFZ #define ffz(bitmask) arch_ffz(bitmask) #else #include "../lib/ffz.h" #endif #ifndef ARCH_HAVE_INIT static inline int arch_init(char *envp[]) { return 0; } #endif #endif fio-2.1.3/backend.c000066400000000000000000001222341222032232000140150ustar00rootroot00000000000000/* * fio - the flexible io tester * * Copyright (C) 2005 Jens Axboe * Copyright (C) 2006-2012 Jens Axboe * * The license below covers all files distributed with fio unless otherwise * noted in the file itself. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fio.h" #ifndef FIO_NO_HAVE_SHM_H #include #endif #include "hash.h" #include "smalloc.h" #include "verify.h" #include "trim.h" #include "diskutil.h" #include "cgroup.h" #include "profile.h" #include "lib/rand.h" #include "memalign.h" #include "server.h" #include "lib/getrusage.h" #include "idletime.h" static pthread_t disk_util_thread; static struct fio_mutex *disk_thread_mutex; static struct fio_mutex *startup_mutex; static struct fio_mutex *writeout_mutex; static struct flist_head *cgroup_list; static char *cgroup_mnt; static int exit_value; static volatile int fio_abort; static unsigned int nr_process = 0; static unsigned int nr_thread = 0; struct io_log *agg_io_log[DDIR_RWDIR_CNT]; int groupid = 0; unsigned int thread_number = 0; unsigned int stat_number = 0; int shm_id = 0; int temp_stall_ts; unsigned long done_secs = 0; volatile int disk_util_exit = 0; #define PAGE_ALIGN(buf) \ (char *) (((uintptr_t) (buf) + page_mask) & ~page_mask) #define JOB_START_TIMEOUT (5 * 1000) static void sig_int(int sig) { if (threads) { if (is_backend) fio_server_got_signal(sig); else { log_info("\nfio: terminating on signal %d\n", sig); fflush(stdout); exit_value = 128; } fio_terminate_threads(TERMINATE_ALL); } } static void sig_show_status(int sig) { show_running_run_stats(); } static void set_sig_handlers(void) { struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = sig_int; act.sa_flags = SA_RESTART; sigaction(SIGINT, &act, NULL); memset(&act, 0, sizeof(act)); act.sa_handler = sig_int; act.sa_flags = SA_RESTART; sigaction(SIGTERM, &act, NULL); /* Windows uses SIGBREAK as a quit signal from other applications */ #ifdef WIN32 memset(&act, 0, sizeof(act)); act.sa_handler = sig_int; act.sa_flags = SA_RESTART; sigaction(SIGBREAK, &act, NULL); #endif memset(&act, 0, sizeof(act)); act.sa_handler = sig_show_status; act.sa_flags = SA_RESTART; sigaction(SIGUSR1, &act, NULL); if (is_backend) { memset(&act, 0, sizeof(act)); act.sa_handler = sig_int; act.sa_flags = SA_RESTART; sigaction(SIGPIPE, &act, NULL); } } /* * Check if we are above the minimum rate given. */ static int __check_min_rate(struct thread_data *td, struct timeval *now, enum fio_ddir ddir) { unsigned long long bytes = 0; unsigned long iops = 0; unsigned long spent; unsigned long rate; unsigned int ratemin = 0; unsigned int rate_iops = 0; unsigned int rate_iops_min = 0; assert(ddir_rw(ddir)); if (!td->o.ratemin[ddir] && !td->o.rate_iops_min[ddir]) return 0; /* * allow a 2 second settle period in the beginning */ if (mtime_since(&td->start, now) < 2000) return 0; iops += td->this_io_blocks[ddir]; bytes += td->this_io_bytes[ddir]; ratemin += td->o.ratemin[ddir]; rate_iops += td->o.rate_iops[ddir]; rate_iops_min += td->o.rate_iops_min[ddir]; /* * if rate blocks is set, sample is running */ if (td->rate_bytes[ddir] || td->rate_blocks[ddir]) { spent = mtime_since(&td->lastrate[ddir], now); if (spent < td->o.ratecycle) return 0; if (td->o.rate[ddir]) { /* * check bandwidth specified rate */ if (bytes < td->rate_bytes[ddir]) { log_err("%s: min rate %u not met\n", td->o.name, ratemin); return 1; } else { rate = ((bytes - td->rate_bytes[ddir]) * 1000) / spent; if (rate < ratemin || bytes < td->rate_bytes[ddir]) { log_err("%s: min rate %u not met, got" " %luKB/sec\n", td->o.name, ratemin, rate); return 1; } } } else { /* * checks iops specified rate */ if (iops < rate_iops) { log_err("%s: min iops rate %u not met\n", td->o.name, rate_iops); return 1; } else { rate = ((iops - td->rate_blocks[ddir]) * 1000) / spent; if (rate < rate_iops_min || iops < td->rate_blocks[ddir]) { log_err("%s: min iops rate %u not met," " got %lu\n", td->o.name, rate_iops_min, rate); } } } } td->rate_bytes[ddir] = bytes; td->rate_blocks[ddir] = iops; memcpy(&td->lastrate[ddir], now, sizeof(*now)); return 0; } static int check_min_rate(struct thread_data *td, struct timeval *now, uint64_t *bytes_done) { int ret = 0; if (bytes_done[DDIR_READ]) ret |= __check_min_rate(td, now, DDIR_READ); if (bytes_done[DDIR_WRITE]) ret |= __check_min_rate(td, now, DDIR_WRITE); if (bytes_done[DDIR_TRIM]) ret |= __check_min_rate(td, now, DDIR_TRIM); return ret; } /* * When job exits, we can cancel the in-flight IO if we are using async * io. Attempt to do so. */ static void cleanup_pending_aio(struct thread_data *td) { int r; /* * get immediately available events, if any */ r = io_u_queued_complete(td, 0, NULL); if (r < 0) return; /* * now cancel remaining active events */ if (td->io_ops->cancel) { struct io_u *io_u; int i; io_u_qiter(&td->io_u_all, io_u, i) { if (io_u->flags & IO_U_F_FLIGHT) { r = td->io_ops->cancel(td, io_u); if (!r) put_io_u(td, io_u); } } } if (td->cur_depth) r = io_u_queued_complete(td, td->cur_depth, NULL); } /* * Helper to handle the final sync of a file. Works just like the normal * io path, just does everything sync. */ static int fio_io_sync(struct thread_data *td, struct fio_file *f) { struct io_u *io_u = __get_io_u(td); int ret; if (!io_u) return 1; io_u->ddir = DDIR_SYNC; io_u->file = f; if (td_io_prep(td, io_u)) { put_io_u(td, io_u); return 1; } requeue: ret = td_io_queue(td, io_u); if (ret < 0) { td_verror(td, io_u->error, "td_io_queue"); put_io_u(td, io_u); return 1; } else if (ret == FIO_Q_QUEUED) { if (io_u_queued_complete(td, 1, NULL) < 0) return 1; } else if (ret == FIO_Q_COMPLETED) { if (io_u->error) { td_verror(td, io_u->error, "td_io_queue"); return 1; } if (io_u_sync_complete(td, io_u, NULL) < 0) return 1; } else if (ret == FIO_Q_BUSY) { if (td_io_commit(td)) return 1; goto requeue; } return 0; } static int fio_file_fsync(struct thread_data *td, struct fio_file *f) { int ret; if (fio_file_open(f)) return fio_io_sync(td, f); if (td_io_open_file(td, f)) return 1; ret = fio_io_sync(td, f); td_io_close_file(td, f); return ret; } static inline void __update_tv_cache(struct thread_data *td) { fio_gettime(&td->tv_cache, NULL); } static inline void update_tv_cache(struct thread_data *td) { if ((++td->tv_cache_nr & td->tv_cache_mask) == td->tv_cache_mask) __update_tv_cache(td); } static inline int runtime_exceeded(struct thread_data *td, struct timeval *t) { if (in_ramp_time(td)) return 0; if (!td->o.timeout) return 0; if (mtime_since(&td->epoch, t) >= td->o.timeout * 1000) return 1; return 0; } static int break_on_this_error(struct thread_data *td, enum fio_ddir ddir, int *retptr) { int ret = *retptr; if (ret < 0 || td->error) { int err = td->error; enum error_type_bit eb; if (ret < 0) err = -ret; eb = td_error_type(ddir, err); if (!(td->o.continue_on_error & (1 << eb))) return 1; if (td_non_fatal_error(td, eb, err)) { /* * Continue with the I/Os in case of * a non fatal error. */ update_error_count(td, err); td_clear_error(td); *retptr = 0; return 0; } else if (td->o.fill_device && err == ENOSPC) { /* * We expect to hit this error if * fill_device option is set. */ td_clear_error(td); td->terminate = 1; return 1; } else { /* * Stop the I/O in case of a fatal * error. */ update_error_count(td, err); return 1; } } return 0; } static void check_update_rusage(struct thread_data *td) { if (td->update_rusage) { td->update_rusage = 0; update_rusage_stat(td); fio_mutex_up(td->rusage_sem); } } /* * The main verify engine. Runs over the writes we previously submitted, * reads the blocks back in, and checks the crc/md5 of the data. */ static void do_verify(struct thread_data *td, uint64_t verify_bytes) { uint64_t bytes_done[DDIR_RWDIR_CNT] = { 0, 0, 0 }; struct fio_file *f; struct io_u *io_u; int ret, min_events; unsigned int i; dprint(FD_VERIFY, "starting loop\n"); /* * sync io first and invalidate cache, to make sure we really * read from disk. */ for_each_file(td, f, i) { if (!fio_file_open(f)) continue; if (fio_io_sync(td, f)) break; if (file_invalidate_cache(td, f)) break; } check_update_rusage(td); if (td->error) return; td_set_runstate(td, TD_VERIFYING); io_u = NULL; while (!td->terminate) { enum fio_ddir ddir; int ret2, full; update_tv_cache(td); check_update_rusage(td); if (runtime_exceeded(td, &td->tv_cache)) { __update_tv_cache(td); if (runtime_exceeded(td, &td->tv_cache)) { td->terminate = 1; break; } } if (flow_threshold_exceeded(td)) continue; if (!td->o.experimental_verify) { io_u = __get_io_u(td); if (!io_u) break; if (get_next_verify(td, io_u)) { put_io_u(td, io_u); break; } if (td_io_prep(td, io_u)) { put_io_u(td, io_u); break; } } else { if (ddir_rw_sum(bytes_done) + td->o.rw_min_bs > verify_bytes) break; while ((io_u = get_io_u(td)) != NULL) { /* * We are only interested in the places where * we wrote or trimmed IOs. Turn those into * reads for verification purposes. */ if (io_u->ddir == DDIR_READ) { /* * Pretend we issued it for rwmix * accounting */ td->io_issues[DDIR_READ]++; put_io_u(td, io_u); continue; } else if (io_u->ddir == DDIR_TRIM) { io_u->ddir = DDIR_READ; io_u->flags |= IO_U_F_TRIMMED; break; } else if (io_u->ddir == DDIR_WRITE) { io_u->ddir = DDIR_READ; break; } else { put_io_u(td, io_u); continue; } } if (!io_u) break; } if (td->o.verify_async) io_u->end_io = verify_io_u_async; else io_u->end_io = verify_io_u; ddir = io_u->ddir; ret = td_io_queue(td, io_u); switch (ret) { case FIO_Q_COMPLETED: if (io_u->error) { ret = -io_u->error; clear_io_u(td, io_u); } else if (io_u->resid) { int bytes = io_u->xfer_buflen - io_u->resid; /* * zero read, fail */ if (!bytes) { td_verror(td, EIO, "full resid"); put_io_u(td, io_u); break; } io_u->xfer_buflen = io_u->resid; io_u->xfer_buf += bytes; io_u->offset += bytes; if (ddir_rw(io_u->ddir)) td->ts.short_io_u[io_u->ddir]++; f = io_u->file; if (io_u->offset == f->real_file_size) goto sync_done; requeue_io_u(td, &io_u); } else { sync_done: ret = io_u_sync_complete(td, io_u, bytes_done); if (ret < 0) break; } continue; case FIO_Q_QUEUED: break; case FIO_Q_BUSY: requeue_io_u(td, &io_u); ret2 = td_io_commit(td); if (ret2 < 0) ret = ret2; break; default: assert(ret < 0); td_verror(td, -ret, "td_io_queue"); break; } if (break_on_this_error(td, ddir, &ret)) break; /* * if we can queue more, do so. but check if there are * completed io_u's first. Note that we can get BUSY even * without IO queued, if the system is resource starved. */ full = queue_full(td) || (ret == FIO_Q_BUSY && td->cur_depth); if (full || !td->o.iodepth_batch_complete) { min_events = min(td->o.iodepth_batch_complete, td->cur_depth); /* * if the queue is full, we MUST reap at least 1 event */ if (full && !min_events) min_events = 1; do { /* * Reap required number of io units, if any, * and do the verification on them through * the callback handler */ if (io_u_queued_complete(td, min_events, bytes_done) < 0) { ret = -1; break; } } while (full && (td->cur_depth > td->o.iodepth_low)); } if (ret < 0) break; } check_update_rusage(td); if (!td->error) { min_events = td->cur_depth; if (min_events) ret = io_u_queued_complete(td, min_events, NULL); } else cleanup_pending_aio(td); td_set_runstate(td, TD_RUNNING); dprint(FD_VERIFY, "exiting loop\n"); } static int io_bytes_exceeded(struct thread_data *td) { unsigned long long bytes; if (td_rw(td)) bytes = td->this_io_bytes[DDIR_READ] + td->this_io_bytes[DDIR_WRITE]; else if (td_write(td)) bytes = td->this_io_bytes[DDIR_WRITE]; else if (td_read(td)) bytes = td->this_io_bytes[DDIR_READ]; else bytes = td->this_io_bytes[DDIR_TRIM]; return bytes >= td->o.size; } /* * Main IO worker function. It retrieves io_u's to process and queues * and reaps them, checking for rate and errors along the way. * * Returns number of bytes written and trimmed. */ static uint64_t do_io(struct thread_data *td) { uint64_t bytes_done[DDIR_RWDIR_CNT] = { 0, 0, 0 }; unsigned int i; int ret = 0; uint64_t bytes_issued = 0; if (in_ramp_time(td)) td_set_runstate(td, TD_RAMP); else td_set_runstate(td, TD_RUNNING); while ((td->o.read_iolog_file && !flist_empty(&td->io_log_list)) || (!flist_empty(&td->trim_list)) || !io_bytes_exceeded(td) || td->o.time_based) { struct timeval comp_time; int min_evts = 0; struct io_u *io_u; int ret2, full; enum fio_ddir ddir; check_update_rusage(td); if (td->terminate || td->done) break; update_tv_cache(td); if (runtime_exceeded(td, &td->tv_cache)) { __update_tv_cache(td); if (runtime_exceeded(td, &td->tv_cache)) { td->terminate = 1; break; } } if (flow_threshold_exceeded(td)) continue; if (bytes_issued >= (uint64_t) td->o.size) break; io_u = get_io_u(td); if (!io_u) break; ddir = io_u->ddir; /* * Add verification end_io handler if: * - Asked to verify (!td_rw(td)) * - Or the io_u is from our verify list (mixed write/ver) */ if (td->o.verify != VERIFY_NONE && io_u->ddir == DDIR_READ && ((io_u->flags & IO_U_F_VER_LIST) || !td_rw(td))) { if (td->o.verify_async) io_u->end_io = verify_io_u_async; else io_u->end_io = verify_io_u; td_set_runstate(td, TD_VERIFYING); } else if (in_ramp_time(td)) td_set_runstate(td, TD_RAMP); else td_set_runstate(td, TD_RUNNING); ret = td_io_queue(td, io_u); switch (ret) { case FIO_Q_COMPLETED: if (io_u->error) { ret = -io_u->error; clear_io_u(td, io_u); } else if (io_u->resid) { int bytes = io_u->xfer_buflen - io_u->resid; struct fio_file *f = io_u->file; bytes_issued += bytes; /* * zero read, fail */ if (!bytes) { td_verror(td, EIO, "full resid"); put_io_u(td, io_u); break; } io_u->xfer_buflen = io_u->resid; io_u->xfer_buf += bytes; io_u->offset += bytes; if (ddir_rw(io_u->ddir)) td->ts.short_io_u[io_u->ddir]++; if (io_u->offset == f->real_file_size) goto sync_done; requeue_io_u(td, &io_u); } else { sync_done: if (__should_check_rate(td, DDIR_READ) || __should_check_rate(td, DDIR_WRITE) || __should_check_rate(td, DDIR_TRIM)) fio_gettime(&comp_time, NULL); ret = io_u_sync_complete(td, io_u, bytes_done); if (ret < 0) break; bytes_issued += io_u->xfer_buflen; } break; case FIO_Q_QUEUED: /* * if the engine doesn't have a commit hook, * the io_u is really queued. if it does have such * a hook, it has to call io_u_queued() itself. */ if (td->io_ops->commit == NULL) io_u_queued(td, io_u); bytes_issued += io_u->xfer_buflen; break; case FIO_Q_BUSY: requeue_io_u(td, &io_u); ret2 = td_io_commit(td); if (ret2 < 0) ret = ret2; break; default: assert(ret < 0); put_io_u(td, io_u); break; } if (break_on_this_error(td, ddir, &ret)) break; /* * See if we need to complete some commands. Note that we * can get BUSY even without IO queued, if the system is * resource starved. */ full = queue_full(td) || (ret == FIO_Q_BUSY && td->cur_depth); if (full || !td->o.iodepth_batch_complete) { min_evts = min(td->o.iodepth_batch_complete, td->cur_depth); /* * if the queue is full, we MUST reap at least 1 event */ if (full && !min_evts) min_evts = 1; if (__should_check_rate(td, DDIR_READ) || __should_check_rate(td, DDIR_WRITE) || __should_check_rate(td, DDIR_TRIM)) fio_gettime(&comp_time, NULL); do { ret = io_u_queued_complete(td, min_evts, bytes_done); if (ret < 0) break; } while (full && (td->cur_depth > td->o.iodepth_low)); } if (ret < 0) break; if (!ddir_rw_sum(bytes_done) && !(td->io_ops->flags & FIO_NOIO)) continue; if (!in_ramp_time(td) && should_check_rate(td, bytes_done)) { if (check_min_rate(td, &comp_time, bytes_done)) { if (exitall_on_terminate) fio_terminate_threads(td->groupid); td_verror(td, EIO, "check_min_rate"); break; } } if (td->o.thinktime) { unsigned long long b; b = ddir_rw_sum(td->io_blocks); if (!(b % td->o.thinktime_blocks)) { int left; io_u_quiesce(td); if (td->o.thinktime_spin) usec_spin(td->o.thinktime_spin); left = td->o.thinktime - td->o.thinktime_spin; if (left) usec_sleep(td, left); } } } check_update_rusage(td); if (td->trim_entries) log_err("fio: %lu trim entries leaked?\n", td->trim_entries); if (td->o.fill_device && td->error == ENOSPC) { td->error = 0; td->terminate = 1; } if (!td->error) { struct fio_file *f; i = td->cur_depth; if (i) { ret = io_u_queued_complete(td, i, bytes_done); if (td->o.fill_device && td->error == ENOSPC) td->error = 0; } if (should_fsync(td) && td->o.end_fsync) { td_set_runstate(td, TD_FSYNCING); for_each_file(td, f, i) { if (!fio_file_fsync(td, f)) continue; log_err("fio: end_fsync failed for file %s\n", f->file_name); } } } else cleanup_pending_aio(td); /* * stop job if we failed doing any IO */ if (!ddir_rw_sum(td->this_io_bytes)) td->done = 1; return bytes_done[DDIR_WRITE] + bytes_done[DDIR_TRIM]; } static void cleanup_io_u(struct thread_data *td) { struct io_u *io_u; while ((io_u = io_u_qpop(&td->io_u_freelist)) != NULL) { if (td->io_ops->io_u_free) td->io_ops->io_u_free(td, io_u); fio_memfree(io_u, sizeof(*io_u)); } free_io_mem(td); io_u_rexit(&td->io_u_requeues); io_u_qexit(&td->io_u_freelist); io_u_qexit(&td->io_u_all); } static int init_io_u(struct thread_data *td) { struct io_u *io_u; unsigned int max_bs, min_write; int cl_align, i, max_units; int data_xfer = 1, err; char *p; max_units = td->o.iodepth; max_bs = td_max_bs(td); min_write = td->o.min_bs[DDIR_WRITE]; td->orig_buffer_size = (unsigned long long) max_bs * (unsigned long long) max_units; if ((td->io_ops->flags & FIO_NOIO) || !(td_read(td) || td_write(td))) data_xfer = 0; err = 0; err += io_u_rinit(&td->io_u_requeues, td->o.iodepth); err += io_u_qinit(&td->io_u_freelist, td->o.iodepth); err += io_u_qinit(&td->io_u_all, td->o.iodepth); if (err) { log_err("fio: failed setting up IO queues\n"); return 1; } /* * if we may later need to do address alignment, then add any * possible adjustment here so that we don't cause a buffer * overflow later. this adjustment may be too much if we get * lucky and the allocator gives us an aligned address. */ if (td->o.odirect || td->o.mem_align || (td->io_ops->flags & FIO_RAWIO)) td->orig_buffer_size += page_mask + td->o.mem_align; if (td->o.mem_type == MEM_SHMHUGE || td->o.mem_type == MEM_MMAPHUGE) { unsigned long bs; bs = td->orig_buffer_size + td->o.hugepage_size - 1; td->orig_buffer_size = bs & ~(td->o.hugepage_size - 1); } if (td->orig_buffer_size != (size_t) td->orig_buffer_size) { log_err("fio: IO memory too large. Reduce max_bs or iodepth\n"); return 1; } if (data_xfer && allocate_io_mem(td)) return 1; if (td->o.odirect || td->o.mem_align || (td->io_ops->flags & FIO_RAWIO)) p = PAGE_ALIGN(td->orig_buffer) + td->o.mem_align; else p = td->orig_buffer; cl_align = os_cache_line_size(); for (i = 0; i < max_units; i++) { void *ptr; if (td->terminate) return 1; ptr = fio_memalign(cl_align, sizeof(*io_u)); if (!ptr) { log_err("fio: unable to allocate aligned memory\n"); break; } io_u = ptr; memset(io_u, 0, sizeof(*io_u)); INIT_FLIST_HEAD(&io_u->verify_list); dprint(FD_MEM, "io_u alloc %p, index %u\n", io_u, i); if (data_xfer) { io_u->buf = p; dprint(FD_MEM, "io_u %p, mem %p\n", io_u, io_u->buf); if (td_write(td)) io_u_fill_buffer(td, io_u, min_write, max_bs); if (td_write(td) && td->o.verify_pattern_bytes) { /* * Fill the buffer with the pattern if we are * going to be doing writes. */ fill_pattern(td, io_u->buf, max_bs, io_u, 0, 0); } } io_u->index = i; io_u->flags = IO_U_F_FREE; io_u_qpush(&td->io_u_freelist, io_u); /* * io_u never leaves this stack, used for iteration of all * io_u buffers. */ io_u_qpush(&td->io_u_all, io_u); if (td->io_ops->io_u_init) { int ret = td->io_ops->io_u_init(td, io_u); if (ret) { log_err("fio: failed to init engine data: %d\n", ret); return 1; } } p += max_bs; } return 0; } static int switch_ioscheduler(struct thread_data *td) { char tmp[256], tmp2[128]; FILE *f; int ret; if (td->io_ops->flags & FIO_DISKLESSIO) return 0; sprintf(tmp, "%s/queue/scheduler", td->sysfs_root); f = fopen(tmp, "r+"); if (!f) { if (errno == ENOENT) { log_err("fio: os or kernel doesn't support IO scheduler" " switching\n"); return 0; } td_verror(td, errno, "fopen iosched"); return 1; } /* * Set io scheduler. */ ret = fwrite(td->o.ioscheduler, strlen(td->o.ioscheduler), 1, f); if (ferror(f) || ret != 1) { td_verror(td, errno, "fwrite"); fclose(f); return 1; } rewind(f); /* * Read back and check that the selected scheduler is now the default. */ ret = fread(tmp, 1, sizeof(tmp), f); if (ferror(f) || ret < 0) { td_verror(td, errno, "fread"); fclose(f); return 1; } sprintf(tmp2, "[%s]", td->o.ioscheduler); if (!strstr(tmp, tmp2)) { log_err("fio: io scheduler %s not found\n", td->o.ioscheduler); td_verror(td, EINVAL, "iosched_switch"); fclose(f); return 1; } fclose(f); return 0; } static int keep_running(struct thread_data *td) { if (td->done) return 0; if (td->o.time_based) return 1; if (td->o.loops) { td->o.loops--; return 1; } if (td->o.size != -1ULL && ddir_rw_sum(td->io_bytes) < td->o.size) { uint64_t diff; /* * If the difference is less than the minimum IO size, we * are done. */ diff = td->o.size - ddir_rw_sum(td->io_bytes); if (diff < td_max_bs(td)) return 0; return 1; } return 0; } static int exec_string(struct thread_options *o, const char *string, const char *mode) { int ret, newlen = strlen(string) + strlen(o->name) + strlen(mode) + 9 + 1; char *str; str = malloc(newlen); sprintf(str, "%s &> %s.%s.txt", string, o->name, mode); log_info("%s : Saving output of %s in %s.%s.txt\n",o->name, mode, o->name, mode); ret = system(str); if (ret == -1) log_err("fio: exec of cmd <%s> failed\n", str); free(str); return ret; } /* * Entry point for the thread based jobs. The process based jobs end up * here as well, after a little setup. */ static void *thread_main(void *data) { unsigned long long elapsed; struct thread_data *td = data; struct thread_options *o = &td->o; pthread_condattr_t attr; int clear_state; int ret; if (!o->use_thread) { setsid(); td->pid = getpid(); } else td->pid = gettid(); fio_local_clock_init(o->use_thread); dprint(FD_PROCESS, "jobs pid=%d started\n", (int) td->pid); if (is_backend) fio_server_send_start(td); INIT_FLIST_HEAD(&td->io_log_list); INIT_FLIST_HEAD(&td->io_hist_list); INIT_FLIST_HEAD(&td->verify_list); INIT_FLIST_HEAD(&td->trim_list); INIT_FLIST_HEAD(&td->next_rand_list); pthread_mutex_init(&td->io_u_lock, NULL); td->io_hist_tree = RB_ROOT; pthread_condattr_init(&attr); pthread_cond_init(&td->verify_cond, &attr); pthread_cond_init(&td->free_cond, &attr); td_set_runstate(td, TD_INITIALIZED); dprint(FD_MUTEX, "up startup_mutex\n"); fio_mutex_up(startup_mutex); dprint(FD_MUTEX, "wait on td->mutex\n"); fio_mutex_down(td->mutex); dprint(FD_MUTEX, "done waiting on td->mutex\n"); /* * the ->mutex mutex is now no longer used, close it to avoid * eating a file descriptor */ fio_mutex_remove(td->mutex); td->mutex = NULL; /* * A new gid requires privilege, so we need to do this before setting * the uid. */ if (o->gid != -1U && setgid(o->gid)) { td_verror(td, errno, "setgid"); goto err; } if (o->uid != -1U && setuid(o->uid)) { td_verror(td, errno, "setuid"); goto err; } /* * If we have a gettimeofday() thread, make sure we exclude that * thread from this job */ if (o->gtod_cpu) fio_cpu_clear(&o->cpumask, o->gtod_cpu); /* * Set affinity first, in case it has an impact on the memory * allocations. */ if (o->cpumask_set) { ret = fio_setaffinity(td->pid, o->cpumask); if (ret == -1) { td_verror(td, errno, "cpu_set_affinity"); goto err; } } #ifdef CONFIG_LIBNUMA /* numa node setup */ if (o->numa_cpumask_set || o->numa_memmask_set) { int ret; if (numa_available() < 0) { td_verror(td, errno, "Does not support NUMA API\n"); goto err; } if (o->numa_cpumask_set) { ret = numa_run_on_node_mask(o->numa_cpunodesmask); if (ret == -1) { td_verror(td, errno, \ "numa_run_on_node_mask failed\n"); goto err; } } if (o->numa_memmask_set) { switch (o->numa_mem_mode) { case MPOL_INTERLEAVE: numa_set_interleave_mask(o->numa_memnodesmask); break; case MPOL_BIND: numa_set_membind(o->numa_memnodesmask); break; case MPOL_LOCAL: numa_set_localalloc(); break; case MPOL_PREFERRED: numa_set_preferred(o->numa_mem_prefer_node); break; case MPOL_DEFAULT: default: break; } } } #endif if (fio_pin_memory(td)) goto err; /* * May alter parameters that init_io_u() will use, so we need to * do this first. */ if (init_iolog(td)) goto err; if (init_io_u(td)) goto err; if (o->verify_async && verify_async_init(td)) goto err; if (o->ioprio) { ret = ioprio_set(IOPRIO_WHO_PROCESS, 0, o->ioprio_class, o->ioprio); if (ret == -1) { td_verror(td, errno, "ioprio_set"); goto err; } } if (o->cgroup && cgroup_setup(td, cgroup_list, &cgroup_mnt)) goto err; errno = 0; if (nice(o->nice) == -1 && errno != 0) { td_verror(td, errno, "nice"); goto err; } if (o->ioscheduler && switch_ioscheduler(td)) goto err; if (!o->create_serialize && setup_files(td)) goto err; if (td_io_init(td)) goto err; if (init_random_map(td)) goto err; if (o->exec_prerun && exec_string(o, o->exec_prerun, (const char *)"prerun")) goto err; if (o->pre_read) { if (pre_read_files(td) < 0) goto err; } fio_verify_init(td); fio_gettime(&td->epoch, NULL); fio_getrusage(&td->ru_start); clear_state = 0; while (keep_running(td)) { uint64_t verify_bytes; fio_gettime(&td->start, NULL); memcpy(&td->bw_sample_time, &td->start, sizeof(td->start)); memcpy(&td->iops_sample_time, &td->start, sizeof(td->start)); memcpy(&td->tv_cache, &td->start, sizeof(td->start)); if (o->ratemin[DDIR_READ] || o->ratemin[DDIR_WRITE] || o->ratemin[DDIR_TRIM]) { memcpy(&td->lastrate[DDIR_READ], &td->bw_sample_time, sizeof(td->bw_sample_time)); memcpy(&td->lastrate[DDIR_WRITE], &td->bw_sample_time, sizeof(td->bw_sample_time)); memcpy(&td->lastrate[DDIR_TRIM], &td->bw_sample_time, sizeof(td->bw_sample_time)); } if (clear_state) clear_io_state(td); prune_io_piece_log(td); verify_bytes = do_io(td); clear_state = 1; if (td_read(td) && td->io_bytes[DDIR_READ]) { elapsed = utime_since_now(&td->start); td->ts.runtime[DDIR_READ] += elapsed; } if (td_write(td) && td->io_bytes[DDIR_WRITE]) { elapsed = utime_since_now(&td->start); td->ts.runtime[DDIR_WRITE] += elapsed; } if (td_trim(td) && td->io_bytes[DDIR_TRIM]) { elapsed = utime_since_now(&td->start); td->ts.runtime[DDIR_TRIM] += elapsed; } if (td->error || td->terminate) break; if (!o->do_verify || o->verify == VERIFY_NONE || (td->io_ops->flags & FIO_UNIDIR)) continue; clear_io_state(td); fio_gettime(&td->start, NULL); do_verify(td, verify_bytes); td->ts.runtime[DDIR_READ] += utime_since_now(&td->start); if (td->error || td->terminate) break; } update_rusage_stat(td); td->ts.runtime[DDIR_READ] = (td->ts.runtime[DDIR_READ] + 999) / 1000; td->ts.runtime[DDIR_WRITE] = (td->ts.runtime[DDIR_WRITE] + 999) / 1000; td->ts.runtime[DDIR_TRIM] = (td->ts.runtime[DDIR_TRIM] + 999) / 1000; td->ts.total_run_time = mtime_since_now(&td->epoch); td->ts.io_bytes[DDIR_READ] = td->io_bytes[DDIR_READ]; td->ts.io_bytes[DDIR_WRITE] = td->io_bytes[DDIR_WRITE]; td->ts.io_bytes[DDIR_TRIM] = td->io_bytes[DDIR_TRIM]; fio_unpin_memory(td); fio_mutex_down(writeout_mutex); if (td->bw_log) { if (o->bw_log_file) { finish_log_named(td, td->bw_log, o->bw_log_file, "bw"); } else finish_log(td, td->bw_log, "bw"); } if (td->lat_log) { if (o->lat_log_file) { finish_log_named(td, td->lat_log, o->lat_log_file, "lat"); } else finish_log(td, td->lat_log, "lat"); } if (td->slat_log) { if (o->lat_log_file) { finish_log_named(td, td->slat_log, o->lat_log_file, "slat"); } else finish_log(td, td->slat_log, "slat"); } if (td->clat_log) { if (o->lat_log_file) { finish_log_named(td, td->clat_log, o->lat_log_file, "clat"); } else finish_log(td, td->clat_log, "clat"); } if (td->iops_log) { if (o->iops_log_file) { finish_log_named(td, td->iops_log, o->iops_log_file, "iops"); } else finish_log(td, td->iops_log, "iops"); } fio_mutex_up(writeout_mutex); if (o->exec_postrun) exec_string(o, o->exec_postrun, (const char *)"postrun"); if (exitall_on_terminate) fio_terminate_threads(td->groupid); err: if (td->error) log_info("fio: pid=%d, err=%d/%s\n", (int) td->pid, td->error, td->verror); if (o->verify_async) verify_async_exit(td); close_and_free_files(td); cleanup_io_u(td); close_ioengine(td); cgroup_shutdown(td, &cgroup_mnt); if (o->cpumask_set) { int ret = fio_cpuset_exit(&o->cpumask); td_verror(td, ret, "fio_cpuset_exit"); } /* * do this very late, it will log file closing as well */ if (o->write_iolog_file) write_iolog_close(td); fio_mutex_remove(td->rusage_sem); td->rusage_sem = NULL; td_set_runstate(td, TD_EXITED); return (void *) (uintptr_t) td->error; } /* * We cannot pass the td data into a forked process, so attach the td and * pass it to the thread worker. */ static int fork_main(int shmid, int offset) { struct thread_data *td; void *data, *ret; #ifndef __hpux data = shmat(shmid, NULL, 0); if (data == (void *) -1) { int __err = errno; perror("shmat"); return __err; } #else /* * HP-UX inherits shm mappings? */ data = threads; #endif td = data + offset * sizeof(struct thread_data); ret = thread_main(td); shmdt(data); return (int) (uintptr_t) ret; } /* * Run over the job map and reap the threads that have exited, if any. */ static void reap_threads(unsigned int *nr_running, unsigned int *t_rate, unsigned int *m_rate) { struct thread_data *td; unsigned int cputhreads, realthreads, pending; int i, status, ret; /* * reap exited threads (TD_EXITED -> TD_REAPED) */ realthreads = pending = cputhreads = 0; for_each_td(td, i) { int flags = 0; /* * ->io_ops is NULL for a thread that has closed its * io engine */ if (td->io_ops && !strcmp(td->io_ops->name, "cpuio")) cputhreads++; else realthreads++; if (!td->pid) { pending++; continue; } if (td->runstate == TD_REAPED) continue; if (td->o.use_thread) { if (td->runstate == TD_EXITED) { td_set_runstate(td, TD_REAPED); goto reaped; } continue; } flags = WNOHANG; if (td->runstate == TD_EXITED) flags = 0; /* * check if someone quit or got killed in an unusual way */ ret = waitpid(td->pid, &status, flags); if (ret < 0) { if (errno == ECHILD) { log_err("fio: pid=%d disappeared %d\n", (int) td->pid, td->runstate); td->sig = ECHILD; td_set_runstate(td, TD_REAPED); goto reaped; } perror("waitpid"); } else if (ret == td->pid) { if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); if (sig != SIGTERM && sig != SIGUSR2) log_err("fio: pid=%d, got signal=%d\n", (int) td->pid, sig); td->sig = sig; td_set_runstate(td, TD_REAPED); goto reaped; } if (WIFEXITED(status)) { if (WEXITSTATUS(status) && !td->error) td->error = WEXITSTATUS(status); td_set_runstate(td, TD_REAPED); goto reaped; } } /* * thread is not dead, continue */ pending++; continue; reaped: (*nr_running)--; (*m_rate) -= ddir_rw_sum(td->o.ratemin); (*t_rate) -= ddir_rw_sum(td->o.rate); if (!td->pid) pending--; if (td->error) exit_value++; done_secs += mtime_since_now(&td->epoch) / 1000; profile_td_exit(td); } if (*nr_running == cputhreads && !pending && realthreads) fio_terminate_threads(TERMINATE_ALL); } static void do_usleep(unsigned int usecs) { check_for_running_stats(); usleep(usecs); } /* * Main function for kicking off and reaping jobs, as needed. */ static void run_threads(void) { struct thread_data *td; unsigned long spent; unsigned int i, todo, nr_running, m_rate, t_rate, nr_started; if (fio_gtod_offload && fio_start_gtod_thread()) return; fio_idle_prof_init(); set_sig_handlers(); nr_thread = nr_process = 0; for_each_td(td, i) { if (td->o.use_thread) nr_thread++; else nr_process++; } if (output_format == FIO_OUTPUT_NORMAL) { log_info("Starting "); if (nr_thread) log_info("%d thread%s", nr_thread, nr_thread > 1 ? "s" : ""); if (nr_process) { if (nr_thread) log_info(" and "); log_info("%d process%s", nr_process, nr_process > 1 ? "es" : ""); } log_info("\n"); fflush(stdout); } todo = thread_number; nr_running = 0; nr_started = 0; m_rate = t_rate = 0; for_each_td(td, i) { print_status_init(td->thread_number - 1); if (!td->o.create_serialize) continue; /* * do file setup here so it happens sequentially, * we don't want X number of threads getting their * client data interspersed on disk */ if (setup_files(td)) { exit_value++; if (td->error) log_err("fio: pid=%d, err=%d/%s\n", (int) td->pid, td->error, td->verror); td_set_runstate(td, TD_REAPED); todo--; } else { struct fio_file *f; unsigned int j; /* * for sharing to work, each job must always open * its own files. so close them, if we opened them * for creation */ for_each_file(td, f, j) { if (fio_file_open(f)) td_io_close_file(td, f); } } } /* start idle threads before io threads start to run */ fio_idle_prof_start(); set_genesis_time(); while (todo) { struct thread_data *map[REAL_MAX_JOBS]; struct timeval this_start; int this_jobs = 0, left; /* * create threads (TD_NOT_CREATED -> TD_CREATED) */ for_each_td(td, i) { if (td->runstate != TD_NOT_CREATED) continue; /* * never got a chance to start, killed by other * thread for some reason */ if (td->terminate) { todo--; continue; } if (td->o.start_delay) { spent = mtime_since_genesis(); if (td->o.start_delay * 1000 > spent) continue; } if (td->o.stonewall && (nr_started || nr_running)) { dprint(FD_PROCESS, "%s: stonewall wait\n", td->o.name); break; } init_disk_util(td); td->rusage_sem = fio_mutex_init(FIO_MUTEX_LOCKED); td->update_rusage = 0; /* * Set state to created. Thread will transition * to TD_INITIALIZED when it's done setting up. */ td_set_runstate(td, TD_CREATED); map[this_jobs++] = td; nr_started++; if (td->o.use_thread) { int ret; dprint(FD_PROCESS, "will pthread_create\n"); ret = pthread_create(&td->thread, NULL, thread_main, td); if (ret) { log_err("pthread_create: %s\n", strerror(ret)); nr_started--; break; } ret = pthread_detach(td->thread); if (ret) log_err("pthread_detach: %s", strerror(ret)); } else { pid_t pid; dprint(FD_PROCESS, "will fork\n"); pid = fork(); if (!pid) { int ret = fork_main(shm_id, i); _exit(ret); } else if (i == fio_debug_jobno) *fio_debug_jobp = pid; } dprint(FD_MUTEX, "wait on startup_mutex\n"); if (fio_mutex_down_timeout(startup_mutex, 10)) { log_err("fio: job startup hung? exiting.\n"); fio_terminate_threads(TERMINATE_ALL); fio_abort = 1; nr_started--; break; } dprint(FD_MUTEX, "done waiting on startup_mutex\n"); } /* * Wait for the started threads to transition to * TD_INITIALIZED. */ fio_gettime(&this_start, NULL); left = this_jobs; while (left && !fio_abort) { if (mtime_since_now(&this_start) > JOB_START_TIMEOUT) break; do_usleep(100000); for (i = 0; i < this_jobs; i++) { td = map[i]; if (!td) continue; if (td->runstate == TD_INITIALIZED) { map[i] = NULL; left--; } else if (td->runstate >= TD_EXITED) { map[i] = NULL; left--; todo--; nr_running++; /* work-around... */ } } } if (left) { log_err("fio: %d job%s failed to start\n", left, left > 1 ? "s" : ""); for (i = 0; i < this_jobs; i++) { td = map[i]; if (!td) continue; kill(td->pid, SIGTERM); } break; } /* * start created threads (TD_INITIALIZED -> TD_RUNNING). */ for_each_td(td, i) { if (td->runstate != TD_INITIALIZED) continue; if (in_ramp_time(td)) td_set_runstate(td, TD_RAMP); else td_set_runstate(td, TD_RUNNING); nr_running++; nr_started--; m_rate += ddir_rw_sum(td->o.ratemin); t_rate += ddir_rw_sum(td->o.rate); todo--; fio_mutex_up(td->mutex); } reap_threads(&nr_running, &t_rate, &m_rate); if (todo) do_usleep(100000); } while (nr_running) { reap_threads(&nr_running, &t_rate, &m_rate); do_usleep(10000); } fio_idle_prof_stop(); update_io_ticks(); } void wait_for_disk_thread_exit(void) { fio_mutex_down(disk_thread_mutex); } static void free_disk_util(void) { disk_util_start_exit(); wait_for_disk_thread_exit(); disk_util_prune_entries(); } static void *disk_thread_main(void *data) { int ret = 0; fio_mutex_up(startup_mutex); while (threads && !ret) { usleep(DISK_UTIL_MSEC * 1000); if (!threads) break; ret = update_io_ticks(); if (!is_backend) print_thread_status(); } fio_mutex_up(disk_thread_mutex); return NULL; } static int create_disk_util_thread(void) { int ret; setup_disk_util(); disk_thread_mutex = fio_mutex_init(FIO_MUTEX_LOCKED); ret = pthread_create(&disk_util_thread, NULL, disk_thread_main, NULL); if (ret) { fio_mutex_remove(disk_thread_mutex); log_err("Can't create disk util thread: %s\n", strerror(ret)); return 1; } ret = pthread_detach(disk_util_thread); if (ret) { fio_mutex_remove(disk_thread_mutex); log_err("Can't detatch disk util thread: %s\n", strerror(ret)); return 1; } dprint(FD_MUTEX, "wait on startup_mutex\n"); fio_mutex_down(startup_mutex); dprint(FD_MUTEX, "done waiting on startup_mutex\n"); return 0; } int fio_backend(void) { struct thread_data *td; int i; if (exec_profile) { if (load_profile(exec_profile)) return 1; free(exec_profile); exec_profile = NULL; } if (!thread_number) return 0; if (write_bw_log) { setup_log(&agg_io_log[DDIR_READ], 0, IO_LOG_TYPE_BW); setup_log(&agg_io_log[DDIR_WRITE], 0, IO_LOG_TYPE_BW); setup_log(&agg_io_log[DDIR_TRIM], 0, IO_LOG_TYPE_BW); } startup_mutex = fio_mutex_init(FIO_MUTEX_LOCKED); if (startup_mutex == NULL) return 1; writeout_mutex = fio_mutex_init(FIO_MUTEX_UNLOCKED); if (writeout_mutex == NULL) return 1; set_genesis_time(); stat_init(); create_disk_util_thread(); cgroup_list = smalloc(sizeof(*cgroup_list)); INIT_FLIST_HEAD(cgroup_list); run_threads(); if (!fio_abort) { show_run_stats(); if (write_bw_log) { __finish_log(agg_io_log[DDIR_READ], "agg-read_bw.log"); __finish_log(agg_io_log[DDIR_WRITE], "agg-write_bw.log"); __finish_log(agg_io_log[DDIR_TRIM], "agg-write_bw.log"); } } for_each_td(td, i) fio_options_free(td); free_disk_util(); cgroup_kill(cgroup_list); sfree(cgroup_list); sfree(cgroup_mnt); fio_mutex_remove(startup_mutex); fio_mutex_remove(writeout_mutex); fio_mutex_remove(disk_thread_mutex); stat_exit(); return exit_value; } fio-2.1.3/blktrace.c000066400000000000000000000236641222032232000142240ustar00rootroot00000000000000/* * blktrace support code for fio */ #include #include #include #include #include "flist.h" #include "fio.h" #include "blktrace_api.h" #define TRACE_FIFO_SIZE 8192 /* * fifo refill frontend, to avoid reading data in trace sized bites */ static int refill_fifo(struct thread_data *td, struct fifo *fifo, int fd) { char buf[TRACE_FIFO_SIZE]; unsigned int total; int ret; total = sizeof(buf); if (total > fifo_room(fifo)) total = fifo_room(fifo); ret = read(fd, buf, total); if (ret < 0) { td_verror(td, errno, "read blktrace file"); return -1; } if (ret > 0) ret = fifo_put(fifo, buf, ret); dprint(FD_BLKTRACE, "refill: filled %d bytes\n", ret); return ret; } /* * Retrieve 'len' bytes from the fifo, refilling if necessary. */ static int trace_fifo_get(struct thread_data *td, struct fifo *fifo, int fd, void *buf, unsigned int len) { if (fifo_len(fifo) < len) { int ret = refill_fifo(td, fifo, fd); if (ret < 0) return ret; } return fifo_get(fifo, buf, len); } /* * Just discard the pdu by seeking past it. */ static int discard_pdu(struct thread_data *td, struct fifo *fifo, int fd, struct blk_io_trace *t) { if (t->pdu_len == 0) return 0; dprint(FD_BLKTRACE, "discard pdu len %u\n", t->pdu_len); return trace_fifo_get(td, fifo, fd, NULL, t->pdu_len); } /* * Check if this is a blktrace binary data file. We read a single trace * into memory and check for the magic signature. */ int is_blktrace(const char *filename) { struct blk_io_trace t; int fd, ret; fd = open(filename, O_RDONLY); if (fd < 0) return 0; ret = read(fd, &t, sizeof(t)); close(fd); if (ret < 0) { perror("read blktrace"); return 0; } else if (ret != sizeof(t)) { log_err("fio: short read on blktrace file\n"); return 0; } if ((t.magic & 0xffffff00) == BLK_IO_TRACE_MAGIC) return 1; return 0; } static int lookup_device(struct thread_data *td, char *path, unsigned int maj, unsigned int min) { struct dirent *dir; struct stat st; int found = 0; DIR *D; D = opendir(path); if (!D) return 0; while ((dir = readdir(D)) != NULL) { char full_path[256]; if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")) continue; sprintf(full_path, "%s%s%s", path, FIO_OS_PATH_SEPARATOR, dir->d_name); if (lstat(full_path, &st) == -1) { perror("lstat"); break; } if (S_ISDIR(st.st_mode)) { found = lookup_device(td, full_path, maj, min); if (found) { strcpy(path, full_path); break; } } if (!S_ISBLK(st.st_mode)) continue; /* * If replay_redirect is set then always return this device * upon lookup which overrides the device lookup based on * major minor in the actual blktrace */ if (td->o.replay_redirect) { dprint(FD_BLKTRACE, "device lookup: %d/%d\n overridden" " with: %s", maj, min, td->o.replay_redirect); strcpy(path, td->o.replay_redirect); found = 1; break; } if (maj == major(st.st_rdev) && min == minor(st.st_rdev)) { dprint(FD_BLKTRACE, "device lookup: %d/%d\n", maj, min); strcpy(path, full_path); found = 1; break; } } closedir(D); return found; } #define FMINORBITS 20 #define FMINORMASK ((1U << FMINORBITS) - 1) #define FMAJOR(dev) ((unsigned int) ((dev) >> FMINORBITS)) #define FMINOR(dev) ((unsigned int) ((dev) & FMINORMASK)) static void trace_add_open_close_event(struct thread_data *td, int fileno, enum file_log_act action) { struct io_piece *ipo; ipo = calloc(1, sizeof(*ipo)); init_ipo(ipo); ipo->ddir = DDIR_INVAL; ipo->fileno = fileno; ipo->file_action = action; flist_add_tail(&ipo->list, &td->io_log_list); } static int trace_add_file(struct thread_data *td, __u32 device) { static unsigned int last_maj, last_min, last_fileno; unsigned int maj = FMAJOR(device); unsigned int min = FMINOR(device); struct fio_file *f; char dev[256]; unsigned int i; if (last_maj == maj && last_min == min) return last_fileno; last_maj = maj; last_min = min; /* * check for this file in our list */ for_each_file(td, f, i) if (f->major == maj && f->minor == min) { last_fileno = f->fileno; return last_fileno; } strcpy(dev, "/dev"); if (lookup_device(td, dev, maj, min)) { int fileno; dprint(FD_BLKTRACE, "add devices %s\n", dev); fileno = add_file_exclusive(td, dev); trace_add_open_close_event(td, fileno, FIO_LOG_OPEN_FILE); last_fileno = fileno; } return last_fileno; } /* * Store blk_io_trace data in an ipo for later retrieval. */ static void store_ipo(struct thread_data *td, unsigned long long offset, unsigned int bytes, int rw, unsigned long long ttime, int fileno) { struct io_piece *ipo = malloc(sizeof(*ipo)); init_ipo(ipo); /* * the 512 is wrong here, it should be the hardware sector size... */ ipo->offset = offset * 512; ipo->len = bytes; ipo->delay = ttime / 1000; if (rw) ipo->ddir = DDIR_WRITE; else ipo->ddir = DDIR_READ; ipo->fileno = fileno; dprint(FD_BLKTRACE, "store ddir=%d, off=%llu, len=%lu, delay=%lu\n", ipo->ddir, ipo->offset, ipo->len, ipo->delay); queue_io_piece(td, ipo); } static void handle_trace_notify(struct blk_io_trace *t) { switch (t->action) { case BLK_TN_PROCESS: printf("got process notify: %x, %d\n", t->action, t->pid); break; case BLK_TN_TIMESTAMP: printf("got timestamp notify: %x, %d\n", t->action, t->pid); break; case BLK_TN_MESSAGE: break; default: dprint(FD_BLKTRACE, "unknown trace act %x\n", t->action); break; } } static void handle_trace_discard(struct thread_data *td, struct blk_io_trace *t, unsigned long long ttime, unsigned long *ios) { struct io_piece *ipo = malloc(sizeof(*ipo)); int fileno; init_ipo(ipo); fileno = trace_add_file(td, t->device); ios[DDIR_WRITE]++; td->o.size += t->bytes; memset(ipo, 0, sizeof(*ipo)); INIT_FLIST_HEAD(&ipo->list); /* * the 512 is wrong here, it should be the hardware sector size... */ ipo->offset = t->sector * 512; ipo->len = t->bytes; ipo->delay = ttime / 1000; ipo->ddir = DDIR_TRIM; ipo->fileno = fileno; dprint(FD_BLKTRACE, "store discard, off=%llu, len=%lu, delay=%lu\n", ipo->offset, ipo->len, ipo->delay); queue_io_piece(td, ipo); } static void handle_trace_fs(struct thread_data *td, struct blk_io_trace *t, unsigned long long ttime, unsigned long *ios, unsigned int *bs) { int rw; int fileno; fileno = trace_add_file(td, t->device); rw = (t->action & BLK_TC_ACT(BLK_TC_WRITE)) != 0; if (t->bytes > bs[rw]) bs[rw] = t->bytes; ios[rw]++; td->o.size += t->bytes; store_ipo(td, t->sector, t->bytes, rw, ttime, fileno); } /* * We only care for queue traces, most of the others are side effects * due to internal workings of the block layer. */ static void handle_trace(struct thread_data *td, struct blk_io_trace *t, unsigned long long ttime, unsigned long *ios, unsigned int *bs) { if ((t->action & 0xffff) != __BLK_TA_QUEUE) return; if (t->action & BLK_TC_ACT(BLK_TC_PC)) return; if (t->action & BLK_TC_ACT(BLK_TC_NOTIFY)) handle_trace_notify(t); else if (t->action & BLK_TC_ACT(BLK_TC_DISCARD)) handle_trace_discard(td, t, ttime, ios); else handle_trace_fs(td, t, ttime, ios, bs); } /* * Load a blktrace file by reading all the blk_io_trace entries, and storing * them as io_pieces like the fio text version would do. */ int load_blktrace(struct thread_data *td, const char *filename) { unsigned long long ttime, delay; struct blk_io_trace t; unsigned long ios[2], skipped_writes; unsigned int cpu; unsigned int rw_bs[2]; struct fifo *fifo; int fd, i; struct fio_file *f; fd = open(filename, O_RDONLY); if (fd < 0) { td_verror(td, errno, "open blktrace file"); return 1; } fifo = fifo_alloc(TRACE_FIFO_SIZE); td->o.size = 0; cpu = 0; ttime = 0; ios[0] = ios[1] = 0; rw_bs[0] = rw_bs[1] = 0; skipped_writes = 0; do { int ret = trace_fifo_get(td, fifo, fd, &t, sizeof(t)); if (ret < 0) goto err; else if (!ret) break; else if (ret < (int) sizeof(t)) { log_err("fio: short fifo get\n"); break; } if ((t.magic & 0xffffff00) != BLK_IO_TRACE_MAGIC) { log_err("fio: bad magic in blktrace data: %x\n", t.magic); goto err; } if ((t.magic & 0xff) != BLK_IO_TRACE_VERSION) { log_err("fio: bad blktrace version %d\n", t.magic & 0xff); goto err; } ret = discard_pdu(td, fifo, fd, &t); if (ret < 0) { td_verror(td, ret, "blktrace lseek"); goto err; } else if (t.pdu_len != ret) { log_err("fio: discarded %d of %d\n", ret, t.pdu_len); goto err; } if ((t.action & BLK_TC_ACT(BLK_TC_NOTIFY)) == 0) { if (!ttime) { ttime = t.time; cpu = t.cpu; } delay = 0; if (cpu == t.cpu) delay = t.time - ttime; if ((t.action & BLK_TC_ACT(BLK_TC_WRITE)) && read_only) skipped_writes++; else { /* * set delay to zero if no_stall enabled for * fast replay */ if (td->o.no_stall) delay = 0; handle_trace(td, &t, delay, ios, rw_bs); } ttime = t.time; cpu = t.cpu; } else { delay = 0; handle_trace(td, &t, delay, ios, rw_bs); } } while (1); for (i = 0; i < td->files_index; i++) { f= td->files[i]; trace_add_open_close_event(td, f->fileno, FIO_LOG_CLOSE_FILE); } fifo_free(fifo); close(fd); if (skipped_writes) log_err("fio: %s skips replay of %lu writes due to read-only\n", td->o.name, skipped_writes); if (!ios[DDIR_READ] && !ios[DDIR_WRITE]) { log_err("fio: found no ios in blktrace data\n"); return 1; } else if (ios[DDIR_READ] && !ios[DDIR_READ]) { td->o.td_ddir = TD_DDIR_READ; td->o.max_bs[DDIR_READ] = rw_bs[DDIR_READ]; } else if (!ios[DDIR_READ] && ios[DDIR_WRITE]) { td->o.td_ddir = TD_DDIR_WRITE; td->o.max_bs[DDIR_WRITE] = rw_bs[DDIR_WRITE]; } else { td->o.td_ddir = TD_DDIR_RW; td->o.max_bs[DDIR_READ] = rw_bs[DDIR_READ]; td->o.max_bs[DDIR_WRITE] = rw_bs[DDIR_WRITE]; } /* * We need to do direct/raw ios to the device, to avoid getting * read-ahead in our way. */ td->o.odirect = 1; return 0; err: close(fd); fifo_free(fifo); return 1; } fio-2.1.3/blktrace_api.h000066400000000000000000000104371222032232000150540ustar00rootroot00000000000000#ifndef BLKTRACEAPI_H #define BLKTRACEAPI_H #include /* * Trace categories */ enum { BLK_TC_READ = 1 << 0, /* reads */ BLK_TC_WRITE = 1 << 1, /* writes */ BLK_TC_BARRIER = 1 << 2, /* barrier */ BLK_TC_SYNC = 1 << 3, /* sync */ BLK_TC_QUEUE = 1 << 4, /* queueing/merging */ BLK_TC_REQUEUE = 1 << 5, /* requeueing */ BLK_TC_ISSUE = 1 << 6, /* issue */ BLK_TC_COMPLETE = 1 << 7, /* completions */ BLK_TC_FS = 1 << 8, /* fs requests */ BLK_TC_PC = 1 << 9, /* pc requests */ BLK_TC_NOTIFY = 1 << 10, /* special message */ BLK_TC_AHEAD = 1 << 11, /* readahead */ BLK_TC_META = 1 << 12, /* metadata */ BLK_TC_DISCARD = 1 << 13, /* discard requests */ BLK_TC_DRV_DATA = 1 << 14, /* binary per-driver data */ BLK_TC_END = 1 << 15, /* only 16-bits, reminder */ }; #define BLK_TC_SHIFT (16) #define BLK_TC_ACT(act) ((act) << BLK_TC_SHIFT) /* * Basic trace actions */ enum { __BLK_TA_QUEUE = 1, /* queued */ __BLK_TA_BACKMERGE, /* back merged to existing rq */ __BLK_TA_FRONTMERGE, /* front merge to existing rq */ __BLK_TA_GETRQ, /* allocated new request */ __BLK_TA_SLEEPRQ, /* sleeping on rq allocation */ __BLK_TA_REQUEUE, /* request requeued */ __BLK_TA_ISSUE, /* sent to driver */ __BLK_TA_COMPLETE, /* completed by driver */ __BLK_TA_PLUG, /* queue was plugged */ __BLK_TA_UNPLUG_IO, /* queue was unplugged by io */ __BLK_TA_UNPLUG_TIMER, /* queue was unplugged by timer */ __BLK_TA_INSERT, /* insert request */ __BLK_TA_SPLIT, /* bio was split */ __BLK_TA_BOUNCE, /* bio was bounced */ __BLK_TA_REMAP, /* bio was remapped */ __BLK_TA_ABORT, /* request aborted */ __BLK_TA_DRV_DATA, /* driver-specific binary data */ }; /* * Notify events. */ enum blktrace_notify { __BLK_TN_PROCESS = 0, /* establish pid/name mapping */ __BLK_TN_TIMESTAMP, /* include system clock */ __BLK_TN_MESSAGE, /* Character string message */ }; /* * Trace actions in full. Additionally, read or write is masked */ #define BLK_TA_QUEUE (__BLK_TA_QUEUE | BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TA_BACKMERGE (__BLK_TA_BACKMERGE | BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TA_FRONTMERGE (__BLK_TA_FRONTMERGE | BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TA_GETRQ (__BLK_TA_GETRQ | BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TA_SLEEPRQ (__BLK_TA_SLEEPRQ | BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TA_REQUEUE (__BLK_TA_REQUEUE | BLK_TC_ACT(BLK_TC_REQUEUE)) #define BLK_TA_ISSUE (__BLK_TA_ISSUE | BLK_TC_ACT(BLK_TC_ISSUE)) #define BLK_TA_COMPLETE (__BLK_TA_COMPLETE| BLK_TC_ACT(BLK_TC_COMPLETE)) #define BLK_TA_PLUG (__BLK_TA_PLUG | BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TA_UNPLUG_IO (__BLK_TA_UNPLUG_IO | BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TA_UNPLUG_TIMER (__BLK_TA_UNPLUG_TIMER | BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TA_INSERT (__BLK_TA_INSERT | BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TA_SPLIT (__BLK_TA_SPLIT) #define BLK_TA_BOUNCE (__BLK_TA_BOUNCE) #define BLK_TA_REMAP (__BLK_TA_REMAP | BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TA_DRV_DATA (__BLK_TA_DRV_DATA | BLK_TC_ACT(BLK_TC_DRV_DATA)) #define BLK_TN_PROCESS (__BLK_TN_PROCESS | BLK_TC_ACT(BLK_TC_NOTIFY)) #define BLK_TN_TIMESTAMP (__BLK_TN_TIMESTAMP | BLK_TC_ACT(BLK_TC_NOTIFY)) #define BLK_TN_MESSAGE (__BLK_TN_MESSAGE | BLK_TC_ACT(BLK_TC_NOTIFY)) #define BLK_IO_TRACE_MAGIC 0x65617400 #define BLK_IO_TRACE_VERSION 0x07 /* * The trace itself */ struct blk_io_trace { __u32 magic; /* MAGIC << 8 | version */ __u32 sequence; /* event number */ __u64 time; /* in nanoseconds */ __u64 sector; /* disk offset */ __u32 bytes; /* transfer length */ __u32 action; /* what happened */ __u32 pid; /* who did it */ __u32 device; /* device identifier (dev_t) */ __u32 cpu; /* on what cpu did it happen */ __u16 error; /* completion error */ __u16 pdu_len; /* length of data after this trace */ }; /* * The remap event */ struct blk_io_trace_remap { __u32 device; __u32 device_from; __u64 sector; }; /* * User setup structure passed with BLKSTARTTRACE */ struct blk_user_trace_setup { char name[32]; /* output */ __u16 act_mask; /* input */ __u32 buf_size; /* input */ __u32 buf_nr; /* input */ __u64 start_lba; __u64 end_lba; __u32 pid; }; #define BLKTRACESETUP _IOWR(0x12,115,struct blk_user_trace_setup) #define BLKTRACESTART _IO(0x12,116) #define BLKTRACESTOP _IO(0x12,117) #define BLKTRACETEARDOWN _IO(0x12,118) #endif fio-2.1.3/cairo_text_helpers.c000066400000000000000000000041631222032232000163110ustar00rootroot00000000000000#include #include #include static void draw_aligned_text(cairo_t *cr, const char *font, double x, double y, double fontsize, const char *text, int alignment) { #define CENTERED 0 #define LEFT_JUSTIFIED 1 #define RIGHT_JUSTIFIED 2 double factor, direction; cairo_text_extents_t extents; switch (alignment) { case CENTERED: direction = -1.0; factor = 0.5; break; case RIGHT_JUSTIFIED: direction = -1.0; factor = 1.0; break; case LEFT_JUSTIFIED: default: direction = 1.0; factor = 0.0; break; } cairo_select_font_face(cr, font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, fontsize); cairo_text_extents(cr, text, &extents); x = x + direction * (factor * extents.width + extents.x_bearing); y = y - (extents.height / 2 + extents.y_bearing); cairo_move_to(cr, x, y); cairo_show_text(cr, text); } void draw_centered_text(cairo_t *cr, const char *font, double x, double y, double fontsize, const char *text) { draw_aligned_text(cr, font, x, y, fontsize, text, CENTERED); } void draw_right_justified_text(cairo_t *cr, const char *font, double x, double y, double fontsize, const char *text) { draw_aligned_text(cr, font, x, y, fontsize, text, RIGHT_JUSTIFIED); } void draw_left_justified_text(cairo_t *cr, const char *font, double x, double y, double fontsize, const char *text) { draw_aligned_text(cr, font, x, y, fontsize, text, LEFT_JUSTIFIED); } void draw_vertical_centered_text(cairo_t *cr, const char *font, double x, double y, double fontsize, const char *text) { double sx, sy; cairo_text_extents_t extents; cairo_select_font_face(cr, font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, fontsize); cairo_text_extents(cr, text, &extents); sx = x; sy = y; y = y + (extents.width / 2.0 + extents.x_bearing); x = x - (extents.height / 2.0 + extents.y_bearing); cairo_move_to(cr, x, y); cairo_save(cr); cairo_translate(cr, -sx, -sy); cairo_rotate(cr, -90.0 * M_PI / 180.0); cairo_translate(cr, sx, sy); cairo_show_text(cr, text); cairo_restore(cr); } fio-2.1.3/cairo_text_helpers.h000066400000000000000000000010731222032232000163130ustar00rootroot00000000000000#ifndef CAIRO_TEXT_HELPERS_H #define CAIRO_TEXT_HELPERS_H void draw_centered_text(cairo_t *cr, const char *font, double x, double y, double fontsize, const char *text); void draw_right_justified_text(cairo_t *cr, const char *font, double x, double y, double fontsize, const char *text); void draw_left_justified_text(cairo_t *cr, const char *font, double x, double y, double fontsize, const char *text); void draw_vertical_centered_text(cairo_t *cr, const char *font, double x, double y, double fontsize, const char *text); #endif fio-2.1.3/cconv.c000066400000000000000000000435621222032232000135440ustar00rootroot00000000000000#include #include "thread_options.h" static void string_to_cpu(char **dst, const uint8_t *src) { const char *__src = (const char *) src; if (strlen(__src)) *dst = strdup(__src); } static void string_to_net(uint8_t *dst, const char *src) { if (src) strcpy((char *) dst, src); else dst[0] = '\0'; } void convert_thread_options_to_cpu(struct thread_options *o, struct thread_options_pack *top) { int i, j; string_to_cpu(&o->description, top->description); string_to_cpu(&o->name, top->name); string_to_cpu(&o->directory, top->directory); string_to_cpu(&o->filename, top->filename); string_to_cpu(&o->filename_format, top->filename_format); string_to_cpu(&o->opendir, top->opendir); string_to_cpu(&o->ioengine, top->ioengine); string_to_cpu(&o->mmapfile, top->mmapfile); string_to_cpu(&o->read_iolog_file, top->read_iolog_file); string_to_cpu(&o->write_iolog_file, top->write_iolog_file); string_to_cpu(&o->bw_log_file, top->bw_log_file); string_to_cpu(&o->lat_log_file, top->lat_log_file); string_to_cpu(&o->iops_log_file, top->iops_log_file); string_to_cpu(&o->replay_redirect, top->replay_redirect); string_to_cpu(&o->exec_prerun, top->exec_prerun); string_to_cpu(&o->exec_postrun, top->exec_postrun); string_to_cpu(&o->ioscheduler, top->ioscheduler); string_to_cpu(&o->profile, top->profile); string_to_cpu(&o->cgroup, top->cgroup); o->td_ddir = le32_to_cpu(top->td_ddir); o->rw_seq = le32_to_cpu(top->rw_seq); o->kb_base = le32_to_cpu(top->kb_base); o->unit_base = le32_to_cpu(top->kb_base); o->ddir_seq_nr = le32_to_cpu(top->ddir_seq_nr); o->ddir_seq_add = le64_to_cpu(top->ddir_seq_add); o->iodepth = le32_to_cpu(top->iodepth); o->iodepth_low = le32_to_cpu(top->iodepth_low); o->iodepth_batch = le32_to_cpu(top->iodepth_batch); o->iodepth_batch_complete = le32_to_cpu(top->iodepth_batch_complete); o->size = le64_to_cpu(top->size); o->size_percent = le32_to_cpu(top->size_percent); o->fill_device = le32_to_cpu(top->fill_device); o->file_size_low = le64_to_cpu(top->file_size_low); o->file_size_high = le64_to_cpu(top->file_size_high); o->start_offset = le64_to_cpu(top->start_offset); for (i = 0; i < DDIR_RWDIR_CNT; i++) { o->bs[i] = le32_to_cpu(top->bs[i]); o->ba[i] = le32_to_cpu(top->ba[i]); o->min_bs[i] = le32_to_cpu(top->min_bs[i]); o->max_bs[i] = le32_to_cpu(top->max_bs[i]); o->bssplit_nr[i] = le32_to_cpu(top->bssplit_nr[i]); if (o->bssplit_nr[i]) { o->bssplit[i] = malloc(o->bssplit_nr[i] * sizeof(struct bssplit)); for (j = 0; j < o->bssplit_nr[i]; j++) { o->bssplit[i][j].bs = le32_to_cpu(top->bssplit[i][j].bs); o->bssplit[i][j].perc = le32_to_cpu(top->bssplit[i][j].perc); } } o->rwmix[i] = le32_to_cpu(top->rwmix[i]); o->rate[i] = le32_to_cpu(top->rate[i]); o->ratemin[i] = le32_to_cpu(top->ratemin[i]); o->rate_iops[i] = le32_to_cpu(top->rate_iops[i]); o->rate_iops_min[i] = le32_to_cpu(top->rate_iops_min[i]); o->perc_rand[i] = le32_to_cpu(top->perc_rand[i]); } o->ratecycle = le32_to_cpu(top->ratecycle); o->nr_files = le32_to_cpu(top->nr_files); o->open_files = le32_to_cpu(top->open_files); o->file_lock_mode = le32_to_cpu(top->file_lock_mode); o->odirect = le32_to_cpu(top->odirect); o->invalidate_cache = le32_to_cpu(top->invalidate_cache); o->create_serialize = le32_to_cpu(top->create_serialize); o->create_fsync = le32_to_cpu(top->create_fsync); o->create_on_open = le32_to_cpu(top->create_on_open); o->create_only = le32_to_cpu(top->create_only); o->end_fsync = le32_to_cpu(top->end_fsync); o->pre_read = le32_to_cpu(top->pre_read); o->sync_io = le32_to_cpu(top->sync_io); o->verify = le32_to_cpu(top->verify); o->do_verify = le32_to_cpu(top->do_verify); o->verifysort = le32_to_cpu(top->verifysort); o->verifysort_nr = le32_to_cpu(top->verifysort_nr); o->experimental_verify = le32_to_cpu(top->experimental_verify); o->verify_interval = le32_to_cpu(top->verify_interval); o->verify_offset = le32_to_cpu(top->verify_offset); memcpy(o->verify_pattern, top->verify_pattern, MAX_PATTERN_SIZE); o->verify_pattern_bytes = le32_to_cpu(top->verify_pattern_bytes); o->verify_fatal = le32_to_cpu(top->verify_fatal); o->verify_dump = le32_to_cpu(top->verify_dump); o->verify_async = le32_to_cpu(top->verify_async); o->verify_batch = le32_to_cpu(top->verify_batch); o->use_thread = le32_to_cpu(top->use_thread); o->unlink = le32_to_cpu(top->unlink); o->do_disk_util = le32_to_cpu(top->do_disk_util); o->override_sync = le32_to_cpu(top->override_sync); o->rand_repeatable = le32_to_cpu(top->rand_repeatable); o->use_os_rand = le32_to_cpu(top->use_os_rand); o->log_avg_msec = le32_to_cpu(top->log_avg_msec); o->norandommap = le32_to_cpu(top->norandommap); o->softrandommap = le32_to_cpu(top->softrandommap); o->bs_unaligned = le32_to_cpu(top->bs_unaligned); o->fsync_on_close = le32_to_cpu(top->fsync_on_close); o->bs_is_seq_rand = le32_to_cpu(top->bs_is_seq_rand); o->random_distribution = le32_to_cpu(top->random_distribution); o->zipf_theta.u.f = fio_uint64_to_double(le64_to_cpu(top->zipf_theta.u.i)); o->pareto_h.u.f = fio_uint64_to_double(le64_to_cpu(top->pareto_h.u.i)); o->random_generator = le32_to_cpu(top->random_generator); o->hugepage_size = le32_to_cpu(top->hugepage_size); o->rw_min_bs = le32_to_cpu(top->rw_min_bs); o->thinktime = le32_to_cpu(top->thinktime); o->thinktime_spin = le32_to_cpu(top->thinktime_spin); o->thinktime_blocks = le32_to_cpu(top->thinktime_blocks); o->fsync_blocks = le32_to_cpu(top->fsync_blocks); o->fdatasync_blocks = le32_to_cpu(top->fdatasync_blocks); o->barrier_blocks = le32_to_cpu(top->barrier_blocks); o->verify_backlog = le64_to_cpu(top->verify_backlog); o->start_delay = le64_to_cpu(top->start_delay); o->timeout = le64_to_cpu(top->timeout); o->ramp_time = le64_to_cpu(top->ramp_time); o->zone_range = le64_to_cpu(top->zone_range); o->zone_size = le64_to_cpu(top->zone_size); o->zone_skip = le64_to_cpu(top->zone_skip); o->lockmem = le64_to_cpu(top->lockmem); o->offset_increment = le64_to_cpu(top->offset_increment); o->number_ios = le64_to_cpu(top->number_ios); o->overwrite = le32_to_cpu(top->overwrite); o->bw_avg_time = le32_to_cpu(top->bw_avg_time); o->iops_avg_time = le32_to_cpu(top->iops_avg_time); o->loops = le32_to_cpu(top->loops); o->mem_type = le32_to_cpu(top->mem_type); o->mem_align = le32_to_cpu(top->mem_align); o->max_latency = le32_to_cpu(top->max_latency); o->stonewall = le32_to_cpu(top->stonewall); o->new_group = le32_to_cpu(top->new_group); o->numjobs = le32_to_cpu(top->numjobs); o->cpumask_set = le32_to_cpu(top->cpumask_set); o->verify_cpumask_set = le32_to_cpu(top->verify_cpumask_set); o->iolog = le32_to_cpu(top->iolog); o->rwmixcycle = le32_to_cpu(top->rwmixcycle); o->nice = le32_to_cpu(top->nice); o->ioprio = le32_to_cpu(top->ioprio); o->ioprio_class = le32_to_cpu(top->ioprio_class); o->file_service_type = le32_to_cpu(top->file_service_type); o->group_reporting = le32_to_cpu(top->group_reporting); o->fadvise_hint = le32_to_cpu(top->fadvise_hint); o->fallocate_mode = le32_to_cpu(top->fallocate_mode); o->zero_buffers = le32_to_cpu(top->zero_buffers); o->refill_buffers = le32_to_cpu(top->refill_buffers); o->scramble_buffers = le32_to_cpu(top->scramble_buffers); o->time_based = le32_to_cpu(top->time_based); o->disable_lat = le32_to_cpu(top->disable_lat); o->disable_clat = le32_to_cpu(top->disable_clat); o->disable_slat = le32_to_cpu(top->disable_slat); o->disable_bw = le32_to_cpu(top->disable_bw); o->unified_rw_rep = le32_to_cpu(top->unified_rw_rep); o->gtod_reduce = le32_to_cpu(top->gtod_reduce); o->gtod_cpu = le32_to_cpu(top->gtod_cpu); o->gtod_offload = le32_to_cpu(top->gtod_offload); o->clocksource = le32_to_cpu(top->clocksource); o->no_stall = le32_to_cpu(top->no_stall); o->trim_percentage = le32_to_cpu(top->trim_percentage); o->trim_batch = le32_to_cpu(top->trim_batch); o->trim_zero = le32_to_cpu(top->trim_zero); o->clat_percentiles = le32_to_cpu(top->clat_percentiles); o->percentile_precision = le32_to_cpu(top->percentile_precision); o->continue_on_error = le32_to_cpu(top->continue_on_error); o->cgroup_weight = le32_to_cpu(top->cgroup_weight); o->cgroup_nodelete = le32_to_cpu(top->cgroup_nodelete); o->uid = le32_to_cpu(top->uid); o->gid = le32_to_cpu(top->gid); o->flow_id = __le32_to_cpu(top->flow_id); o->flow = __le32_to_cpu(top->flow); o->flow_watermark = __le32_to_cpu(top->flow_watermark); o->flow_sleep = le32_to_cpu(top->flow_sleep); o->sync_file_range = le32_to_cpu(top->sync_file_range); o->compress_percentage = le32_to_cpu(top->compress_percentage); o->compress_chunk = le32_to_cpu(top->compress_chunk); o->trim_backlog = le64_to_cpu(top->trim_backlog); for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) o->percentile_list[i].u.f = fio_uint64_to_double(le64_to_cpu(top->percentile_list[i].u.i)); #if 0 uint8_t cpumask[FIO_TOP_STR_MAX]; uint8_t verify_cpumask[FIO_TOP_STR_MAX]; #endif } void convert_thread_options_to_net(struct thread_options_pack *top, struct thread_options *o) { int i, j; string_to_net(top->description, o->description); string_to_net(top->name, o->name); string_to_net(top->directory, o->directory); string_to_net(top->filename, o->filename); string_to_net(top->filename_format, o->filename_format); string_to_net(top->opendir, o->opendir); string_to_net(top->ioengine, o->ioengine); string_to_net(top->mmapfile, o->mmapfile); string_to_net(top->read_iolog_file, o->read_iolog_file); string_to_net(top->write_iolog_file, o->write_iolog_file); string_to_net(top->bw_log_file, o->bw_log_file); string_to_net(top->lat_log_file, o->lat_log_file); string_to_net(top->iops_log_file, o->iops_log_file); string_to_net(top->replay_redirect, o->replay_redirect); string_to_net(top->exec_prerun, o->exec_prerun); string_to_net(top->exec_postrun, o->exec_postrun); string_to_net(top->ioscheduler, o->ioscheduler); string_to_net(top->profile, o->profile); string_to_net(top->cgroup, o->cgroup); top->td_ddir = cpu_to_le32(o->td_ddir); top->rw_seq = cpu_to_le32(o->rw_seq); top->kb_base = cpu_to_le32(o->kb_base); top->unit_base = cpu_to_le32(o->kb_base); top->ddir_seq_nr = cpu_to_le32(o->ddir_seq_nr); top->iodepth = cpu_to_le32(o->iodepth); top->iodepth_low = cpu_to_le32(o->iodepth_low); top->iodepth_batch = cpu_to_le32(o->iodepth_batch); top->iodepth_batch_complete = cpu_to_le32(o->iodepth_batch_complete); top->size_percent = cpu_to_le32(o->size_percent); top->fill_device = cpu_to_le32(o->fill_device); top->ratecycle = cpu_to_le32(o->ratecycle); top->nr_files = cpu_to_le32(o->nr_files); top->open_files = cpu_to_le32(o->open_files); top->file_lock_mode = cpu_to_le32(o->file_lock_mode); top->odirect = cpu_to_le32(o->odirect); top->invalidate_cache = cpu_to_le32(o->invalidate_cache); top->create_serialize = cpu_to_le32(o->create_serialize); top->create_fsync = cpu_to_le32(o->create_fsync); top->create_on_open = cpu_to_le32(o->create_on_open); top->create_only = cpu_to_le32(o->create_only); top->end_fsync = cpu_to_le32(o->end_fsync); top->pre_read = cpu_to_le32(o->pre_read); top->sync_io = cpu_to_le32(o->sync_io); top->verify = cpu_to_le32(o->verify); top->do_verify = cpu_to_le32(o->do_verify); top->verifysort = cpu_to_le32(o->verifysort); top->verifysort_nr = cpu_to_le32(o->verifysort_nr); top->experimental_verify = cpu_to_le32(o->experimental_verify); top->verify_interval = cpu_to_le32(o->verify_interval); top->verify_offset = cpu_to_le32(o->verify_offset); top->verify_pattern_bytes = cpu_to_le32(o->verify_pattern_bytes); top->verify_fatal = cpu_to_le32(o->verify_fatal); top->verify_dump = cpu_to_le32(o->verify_dump); top->verify_async = cpu_to_le32(o->verify_async); top->verify_batch = cpu_to_le32(o->verify_batch); top->use_thread = cpu_to_le32(o->use_thread); top->unlink = cpu_to_le32(o->unlink); top->do_disk_util = cpu_to_le32(o->do_disk_util); top->override_sync = cpu_to_le32(o->override_sync); top->rand_repeatable = cpu_to_le32(o->rand_repeatable); top->use_os_rand = cpu_to_le32(o->use_os_rand); top->log_avg_msec = cpu_to_le32(o->log_avg_msec); top->norandommap = cpu_to_le32(o->norandommap); top->softrandommap = cpu_to_le32(o->softrandommap); top->bs_unaligned = cpu_to_le32(o->bs_unaligned); top->fsync_on_close = cpu_to_le32(o->fsync_on_close); top->bs_is_seq_rand = cpu_to_le32(o->bs_is_seq_rand); top->random_distribution = cpu_to_le32(o->random_distribution); top->zipf_theta.u.i = __cpu_to_le64(fio_double_to_uint64(o->zipf_theta.u.f)); top->pareto_h.u.i = __cpu_to_le64(fio_double_to_uint64(o->pareto_h.u.f)); top->random_generator = cpu_to_le32(o->random_generator); top->hugepage_size = cpu_to_le32(o->hugepage_size); top->rw_min_bs = cpu_to_le32(o->rw_min_bs); top->thinktime = cpu_to_le32(o->thinktime); top->thinktime_spin = cpu_to_le32(o->thinktime_spin); top->thinktime_blocks = cpu_to_le32(o->thinktime_blocks); top->fsync_blocks = cpu_to_le32(o->fsync_blocks); top->fdatasync_blocks = cpu_to_le32(o->fdatasync_blocks); top->barrier_blocks = cpu_to_le32(o->barrier_blocks); top->overwrite = cpu_to_le32(o->overwrite); top->bw_avg_time = cpu_to_le32(o->bw_avg_time); top->iops_avg_time = cpu_to_le32(o->iops_avg_time); top->loops = cpu_to_le32(o->loops); top->mem_type = cpu_to_le32(o->mem_type); top->mem_align = cpu_to_le32(o->mem_align); top->max_latency = cpu_to_le32(o->max_latency); top->stonewall = cpu_to_le32(o->stonewall); top->new_group = cpu_to_le32(o->new_group); top->numjobs = cpu_to_le32(o->numjobs); top->cpumask_set = cpu_to_le32(o->cpumask_set); top->verify_cpumask_set = cpu_to_le32(o->verify_cpumask_set); top->iolog = cpu_to_le32(o->iolog); top->rwmixcycle = cpu_to_le32(o->rwmixcycle); top->nice = cpu_to_le32(o->nice); top->ioprio = cpu_to_le32(o->ioprio); top->ioprio_class = cpu_to_le32(o->ioprio_class); top->file_service_type = cpu_to_le32(o->file_service_type); top->group_reporting = cpu_to_le32(o->group_reporting); top->fadvise_hint = cpu_to_le32(o->fadvise_hint); top->fallocate_mode = cpu_to_le32(o->fallocate_mode); top->zero_buffers = cpu_to_le32(o->zero_buffers); top->refill_buffers = cpu_to_le32(o->refill_buffers); top->scramble_buffers = cpu_to_le32(o->scramble_buffers); top->time_based = cpu_to_le32(o->time_based); top->disable_lat = cpu_to_le32(o->disable_lat); top->disable_clat = cpu_to_le32(o->disable_clat); top->disable_slat = cpu_to_le32(o->disable_slat); top->disable_bw = cpu_to_le32(o->disable_bw); top->unified_rw_rep = cpu_to_le32(o->unified_rw_rep); top->gtod_reduce = cpu_to_le32(o->gtod_reduce); top->gtod_cpu = cpu_to_le32(o->gtod_cpu); top->gtod_offload = cpu_to_le32(o->gtod_offload); top->clocksource = cpu_to_le32(o->clocksource); top->no_stall = cpu_to_le32(o->no_stall); top->trim_percentage = cpu_to_le32(o->trim_percentage); top->trim_batch = cpu_to_le32(o->trim_batch); top->trim_zero = cpu_to_le32(o->trim_zero); top->clat_percentiles = cpu_to_le32(o->clat_percentiles); top->percentile_precision = cpu_to_le32(o->percentile_precision); top->continue_on_error = cpu_to_le32(o->continue_on_error); top->cgroup_weight = cpu_to_le32(o->cgroup_weight); top->cgroup_nodelete = cpu_to_le32(o->cgroup_nodelete); top->uid = cpu_to_le32(o->uid); top->gid = cpu_to_le32(o->gid); top->flow_id = __cpu_to_le32(o->flow_id); top->flow = __cpu_to_le32(o->flow); top->flow_watermark = __cpu_to_le32(o->flow_watermark); top->flow_sleep = cpu_to_le32(o->flow_sleep); top->sync_file_range = cpu_to_le32(o->sync_file_range); top->compress_percentage = cpu_to_le32(o->compress_percentage); top->compress_chunk = cpu_to_le32(o->compress_chunk); for (i = 0; i < DDIR_RWDIR_CNT; i++) { top->bs[i] = cpu_to_le32(o->bs[i]); top->ba[i] = cpu_to_le32(o->ba[i]); top->min_bs[i] = cpu_to_le32(o->min_bs[i]); top->max_bs[i] = cpu_to_le32(o->max_bs[i]); top->bssplit_nr[i] = cpu_to_le32(o->bssplit_nr[i]); if (o->bssplit_nr[i]) { unsigned int bssplit_nr = o->bssplit_nr[i]; if (bssplit_nr > BSSPLIT_MAX) { log_err("fio: BSSPLIT_MAX is too small\n"); bssplit_nr = BSSPLIT_MAX; } for (j = 0; j < bssplit_nr; j++) { top->bssplit[i][j].bs = cpu_to_le32(o->bssplit[i][j].bs); top->bssplit[i][j].perc = cpu_to_le32(o->bssplit[i][j].perc); } } top->rwmix[i] = cpu_to_le32(o->rwmix[i]); top->rate[i] = cpu_to_le32(o->rate[i]); top->ratemin[i] = cpu_to_le32(o->ratemin[i]); top->rate_iops[i] = cpu_to_le32(o->rate_iops[i]); top->rate_iops_min[i] = cpu_to_le32(o->rate_iops_min[i]); top->perc_rand[i] = cpu_to_le32(o->perc_rand[i]); } memcpy(top->verify_pattern, o->verify_pattern, MAX_PATTERN_SIZE); top->size = __cpu_to_le64(o->size); top->verify_backlog = __cpu_to_le64(o->verify_backlog); top->start_delay = __cpu_to_le64(o->start_delay); top->timeout = __cpu_to_le64(o->timeout); top->ramp_time = __cpu_to_le64(o->ramp_time); top->zone_range = __cpu_to_le64(o->zone_range); top->zone_size = __cpu_to_le64(o->zone_size); top->zone_skip = __cpu_to_le64(o->zone_skip); top->lockmem = __cpu_to_le64(o->lockmem); top->ddir_seq_add = __cpu_to_le64(o->ddir_seq_add); top->file_size_low = __cpu_to_le64(o->file_size_low); top->file_size_high = __cpu_to_le64(o->file_size_high); top->start_offset = __cpu_to_le64(o->start_offset); top->trim_backlog = __cpu_to_le64(o->trim_backlog); top->offset_increment = __cpu_to_le64(o->offset_increment); top->number_ios = __cpu_to_le64(o->number_ios); for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) top->percentile_list[i].u.i = __cpu_to_le64(fio_double_to_uint64(o->percentile_list[i].u.f)); #if 0 uint8_t cpumask[FIO_TOP_STR_MAX]; uint8_t verify_cpumask[FIO_TOP_STR_MAX]; #endif } /* * Basic conversion test. We'd really need to fill in more of the options * to have a thorough test. Even better, we should auto-generate the * converter functions... */ int fio_test_cconv(struct thread_options *__o) { struct thread_options o; struct thread_options_pack top1, top2; memset(&top1, 0, sizeof(top1)); memset(&top2, 0, sizeof(top2)); convert_thread_options_to_net(&top1, __o); memset(&o, 0, sizeof(o)); convert_thread_options_to_cpu(&o, &top1); convert_thread_options_to_net(&top2, &o); return memcmp(&top1, &top2, sizeof(top1)); } fio-2.1.3/cgroup.c000066400000000000000000000074311222032232000137260ustar00rootroot00000000000000/* * Code related to setting up a blkio cgroup */ #include #include #include #include #include #include "fio.h" #include "flist.h" #include "cgroup.h" #include "smalloc.h" static struct fio_mutex *lock; struct cgroup_member { struct flist_head list; char *root; unsigned int cgroup_nodelete; }; static char *find_cgroup_mnt(struct thread_data *td) { char *mntpoint = NULL; struct mntent *mnt, dummy; char buf[256] = {0}; FILE *f; f = setmntent("/proc/mounts", "r"); if (!f) { td_verror(td, errno, "setmntent /proc/mounts"); return NULL; } while ((mnt = getmntent_r(f, &dummy, buf, sizeof(buf))) != NULL) { if (!strcmp(mnt->mnt_type, "cgroup") && strstr(mnt->mnt_opts, "blkio")) break; } if (mnt) mntpoint = smalloc_strdup(mnt->mnt_dir); else log_err("fio: cgroup blkio does not appear to be mounted\n"); endmntent(f); return mntpoint; } static void add_cgroup(struct thread_data *td, const char *name, struct flist_head *clist) { struct cgroup_member *cm; if (!lock) return; cm = smalloc(sizeof(*cm)); if (!cm) { err: log_err("fio: failed to allocate cgroup member\n"); return; } INIT_FLIST_HEAD(&cm->list); cm->root = smalloc_strdup(name); if (!cm->root) { sfree(cm); goto err; } if (td->o.cgroup_nodelete) cm->cgroup_nodelete = 1; fio_mutex_down(lock); flist_add_tail(&cm->list, clist); fio_mutex_up(lock); } void cgroup_kill(struct flist_head *clist) { struct flist_head *n, *tmp; struct cgroup_member *cm; if (!lock) return; fio_mutex_down(lock); flist_for_each_safe(n, tmp, clist) { cm = flist_entry(n, struct cgroup_member, list); if (!cm->cgroup_nodelete) rmdir(cm->root); flist_del(&cm->list); sfree(cm->root); sfree(cm); } fio_mutex_up(lock); } static char *get_cgroup_root(struct thread_data *td, char *mnt) { char *str = malloc(64); if (td->o.cgroup) sprintf(str, "%s%s%s", mnt, FIO_OS_PATH_SEPARATOR, td->o.cgroup); else sprintf(str, "%s%s%s", mnt, FIO_OS_PATH_SEPARATOR, td->o.name); return str; } static int write_int_to_file(struct thread_data *td, const char *path, const char *filename, unsigned int val, const char *onerr) { char tmp[256]; FILE *f; sprintf(tmp, "%s%s%s", path, FIO_OS_PATH_SEPARATOR, filename); f = fopen(tmp, "w"); if (!f) { td_verror(td, errno, onerr); return 1; } fprintf(f, "%u", val); fclose(f); return 0; } static int cgroup_write_pid(struct thread_data *td, const char *root) { unsigned int val = td->pid; return write_int_to_file(td, root, "tasks", val, "cgroup write pid"); } /* * Move pid to root class */ static int cgroup_del_pid(struct thread_data *td, char *mnt) { return cgroup_write_pid(td, mnt); } int cgroup_setup(struct thread_data *td, struct flist_head *clist, char **mnt) { char *root; if (!*mnt) { *mnt = find_cgroup_mnt(td); if (!*mnt) return 1; } /* * Create container, if it doesn't exist */ root = get_cgroup_root(td, *mnt); if (mkdir(root, 0755) < 0) { int __e = errno; if (__e != EEXIST) { td_verror(td, __e, "cgroup mkdir"); log_err("fio: path %s\n", root); goto err; } } else add_cgroup(td, root, clist); if (td->o.cgroup_weight) { if (write_int_to_file(td, root, "blkio.weight", td->o.cgroup_weight, "cgroup open weight")) goto err; } if (!cgroup_write_pid(td, root)) { free(root); return 0; } err: free(root); return 1; } void cgroup_shutdown(struct thread_data *td, char **mnt) { if (*mnt == NULL) return; if (!td->o.cgroup_weight && !td->o.cgroup) return; cgroup_del_pid(td, *mnt); } static void fio_init cgroup_init(void) { lock = fio_mutex_init(FIO_MUTEX_UNLOCKED); if (!lock) log_err("fio: failed to allocate cgroup lock\n"); } static void fio_exit cgroup_exit(void) { fio_mutex_remove(lock); } fio-2.1.3/cgroup.h000066400000000000000000000010501222032232000137220ustar00rootroot00000000000000#ifndef FIO_CGROUP_H #define FIO_CGROUP_H #ifdef FIO_HAVE_CGROUPS int cgroup_setup(struct thread_data *, struct flist_head *, char **); void cgroup_shutdown(struct thread_data *, char **); void cgroup_kill(struct flist_head *list); #else static inline int cgroup_setup(struct thread_data *td, struct flist_head *list, char **mnt) { td_verror(td, EINVAL, "cgroup_setup"); return 1; } static inline void cgroup_shutdown(struct thread_data *td, char **mnt) { } static inline void cgroup_kill(struct flist_head *list) { } #endif #endif fio-2.1.3/client.c000066400000000000000000001040461222032232000137050ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_ZLIB #include #endif #include "fio.h" #include "client.h" #include "server.h" #include "flist.h" #include "hash.h" static void handle_du(struct fio_client *client, struct fio_net_cmd *cmd); static void handle_ts(struct fio_client *client, struct fio_net_cmd *cmd); static void handle_gs(struct fio_client *client, struct fio_net_cmd *cmd); static void handle_probe(struct fio_client *client, struct fio_net_cmd *cmd); static void handle_text(struct fio_client *client, struct fio_net_cmd *cmd); static void handle_stop(struct fio_client *client, struct fio_net_cmd *cmd); static void handle_start(struct fio_client *client, struct fio_net_cmd *cmd); struct client_ops fio_client_ops = { .text = handle_text, .disk_util = handle_du, .thread_status = handle_ts, .group_stats = handle_gs, .stop = handle_stop, .start = handle_start, .eta = display_thread_status, .probe = handle_probe, .eta_msec = FIO_CLIENT_DEF_ETA_MSEC, .client_type = FIO_CLIENT_TYPE_CLI, }; static struct timeval eta_tv; static FLIST_HEAD(client_list); static FLIST_HEAD(eta_list); static FLIST_HEAD(arg_list); struct thread_stat client_ts; struct group_run_stats client_gs; int sum_stat_clients; static int sum_stat_nr; static int do_output_all_clients; #define FIO_CLIENT_HASH_BITS 7 #define FIO_CLIENT_HASH_SZ (1 << FIO_CLIENT_HASH_BITS) #define FIO_CLIENT_HASH_MASK (FIO_CLIENT_HASH_SZ - 1) static struct flist_head client_hash[FIO_CLIENT_HASH_SZ]; static void fio_client_add_hash(struct fio_client *client) { int bucket = hash_long(client->fd, FIO_CLIENT_HASH_BITS); bucket &= FIO_CLIENT_HASH_MASK; flist_add(&client->hash_list, &client_hash[bucket]); } static void fio_client_remove_hash(struct fio_client *client) { if (!flist_empty(&client->hash_list)) flist_del_init(&client->hash_list); } static void fio_init fio_client_hash_init(void) { int i; for (i = 0; i < FIO_CLIENT_HASH_SZ; i++) INIT_FLIST_HEAD(&client_hash[i]); } static struct fio_client *find_client_by_fd(int fd) { int bucket = hash_long(fd, FIO_CLIENT_HASH_BITS) & FIO_CLIENT_HASH_MASK; struct fio_client *client; struct flist_head *entry; flist_for_each(entry, &client_hash[bucket]) { client = flist_entry(entry, struct fio_client, hash_list); if (client->fd == fd) { client->refs++; return client; } } return NULL; } void fio_put_client(struct fio_client *client) { if (--client->refs) return; free(client->hostname); if (client->argv) free(client->argv); if (client->name) free(client->name); while (client->nr_ini_file) free(client->ini_file[--client->nr_ini_file]); if (client->ini_file) free(client->ini_file); if (!client->did_stat) sum_stat_clients -= client->nr_stat; free(client); } static void remove_client(struct fio_client *client) { assert(client->refs); dprint(FD_NET, "client: removed <%s>\n", client->hostname); if (!flist_empty(&client->list)) flist_del_init(&client->list); fio_client_remove_hash(client); if (!flist_empty(&client->eta_list)) { flist_del_init(&client->eta_list); fio_client_dec_jobs_eta(client->eta_in_flight, client->ops->eta); } close(client->fd); client->fd = -1; if (client->ops->removed) client->ops->removed(client); nr_clients--; fio_put_client(client); } struct fio_client *fio_get_client(struct fio_client *client) { client->refs++; return client; } static void __fio_client_add_cmd_option(struct fio_client *client, const char *opt) { int index; index = client->argc++; client->argv = realloc(client->argv, sizeof(char *) * client->argc); client->argv[index] = strdup(opt); dprint(FD_NET, "client: add cmd %d: %s\n", index, opt); } void fio_client_add_cmd_option(void *cookie, const char *opt) { struct fio_client *client = cookie; struct flist_head *entry; if (!client || !opt) return; __fio_client_add_cmd_option(client, opt); /* * Duplicate arguments to shared client group */ flist_for_each(entry, &arg_list) { client = flist_entry(entry, struct fio_client, arg_list); __fio_client_add_cmd_option(client, opt); } } struct fio_client *fio_client_add_explicit(struct client_ops *ops, const char *hostname, int type, int port) { struct fio_client *client; client = malloc(sizeof(*client)); memset(client, 0, sizeof(*client)); INIT_FLIST_HEAD(&client->list); INIT_FLIST_HEAD(&client->hash_list); INIT_FLIST_HEAD(&client->arg_list); INIT_FLIST_HEAD(&client->eta_list); INIT_FLIST_HEAD(&client->cmd_list); client->hostname = strdup(hostname); if (type == Fio_client_socket) client->is_sock = 1; else { int ipv6; ipv6 = type == Fio_client_ipv6; if (fio_server_parse_host(hostname, &ipv6, &client->addr.sin_addr, &client->addr6.sin6_addr)) goto err; client->port = port; } client->fd = -1; client->ops = ops; client->refs = 1; client->type = ops->client_type; __fio_client_add_cmd_option(client, "fio"); flist_add(&client->list, &client_list); nr_clients++; dprint(FD_NET, "client: added <%s>\n", client->hostname); return client; err: free(client); return NULL; } void fio_client_add_ini_file(void *cookie, const char *ini_file) { struct fio_client *client = cookie; size_t new_size; dprint(FD_NET, "client <%s>: add ini %s\n", client->hostname, ini_file); new_size = (client->nr_ini_file + 1) * sizeof(char *); client->ini_file = realloc(client->ini_file, new_size); client->ini_file[client->nr_ini_file] = strdup(ini_file); client->nr_ini_file++; } int fio_client_add(struct client_ops *ops, const char *hostname, void **cookie) { struct fio_client *existing = *cookie; struct fio_client *client; if (existing) { /* * We always add our "exec" name as the option, hence 1 * means empty. */ if (existing->argc == 1) flist_add_tail(&existing->arg_list, &arg_list); else { while (!flist_empty(&arg_list)) flist_del_init(arg_list.next); } } client = malloc(sizeof(*client)); memset(client, 0, sizeof(*client)); INIT_FLIST_HEAD(&client->list); INIT_FLIST_HEAD(&client->hash_list); INIT_FLIST_HEAD(&client->arg_list); INIT_FLIST_HEAD(&client->eta_list); INIT_FLIST_HEAD(&client->cmd_list); if (fio_server_parse_string(hostname, &client->hostname, &client->is_sock, &client->port, &client->addr.sin_addr, &client->addr6.sin6_addr, &client->ipv6)) return -1; client->fd = -1; client->ops = ops; client->refs = 1; client->type = ops->client_type; __fio_client_add_cmd_option(client, "fio"); flist_add(&client->list, &client_list); nr_clients++; dprint(FD_NET, "client: added <%s>\n", client->hostname); *cookie = client; return 0; } static void probe_client(struct fio_client *client) { struct cmd_client_probe_pdu pdu; uint64_t tag; dprint(FD_NET, "client: send probe\n"); #ifdef CONFIG_ZLIB pdu.flags = __le64_to_cpu(FIO_PROBE_FLAG_ZLIB); #else pdu.flags = 0; #endif fio_net_send_cmd(client->fd, FIO_NET_CMD_PROBE, &pdu, sizeof(pdu), &tag, &client->cmd_list); } static int fio_client_connect_ip(struct fio_client *client) { struct sockaddr *addr; socklen_t socklen; int fd, domain; if (client->ipv6) { client->addr6.sin6_family = AF_INET6; client->addr6.sin6_port = htons(client->port); domain = AF_INET6; addr = (struct sockaddr *) &client->addr6; socklen = sizeof(client->addr6); } else { client->addr.sin_family = AF_INET; client->addr.sin_port = htons(client->port); domain = AF_INET; addr = (struct sockaddr *) &client->addr; socklen = sizeof(client->addr); } fd = socket(domain, SOCK_STREAM, 0); if (fd < 0) { int ret = -errno; log_err("fio: socket: %s\n", strerror(errno)); return ret; } if (connect(fd, addr, socklen) < 0) { int ret = -errno; log_err("fio: connect: %s\n", strerror(errno)); log_err("fio: failed to connect to %s:%u\n", client->hostname, client->port); close(fd); return ret; } return fd; } static int fio_client_connect_sock(struct fio_client *client) { struct sockaddr_un *addr = &client->addr_un; socklen_t len; int fd; memset(addr, 0, sizeof(*addr)); addr->sun_family = AF_UNIX; strcpy(addr->sun_path, client->hostname); fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { int ret = -errno; log_err("fio: socket: %s\n", strerror(errno)); return ret; } len = sizeof(addr->sun_family) + strlen(addr->sun_path) + 1; if (connect(fd, (struct sockaddr *) addr, len) < 0) { int ret = -errno; log_err("fio: connect; %s\n", strerror(errno)); close(fd); return ret; } return fd; } int fio_client_connect(struct fio_client *client) { int fd; dprint(FD_NET, "client: connect to host %s\n", client->hostname); if (client->is_sock) fd = fio_client_connect_sock(client); else fd = fio_client_connect_ip(client); dprint(FD_NET, "client: %s connected %d\n", client->hostname, fd); if (fd < 0) return fd; client->fd = fd; fio_client_add_hash(client); client->state = Client_connected; probe_client(client); return 0; } int fio_client_terminate(struct fio_client *client) { return fio_net_send_quit(client->fd); } void fio_clients_terminate(void) { struct flist_head *entry; struct fio_client *client; dprint(FD_NET, "client: terminate clients\n"); flist_for_each(entry, &client_list) { client = flist_entry(entry, struct fio_client, list); fio_client_terminate(client); } } static void sig_int(int sig) { dprint(FD_NET, "client: got signal %d\n", sig); fio_clients_terminate(); } static void sig_show_status(int sig) { show_running_run_stats(); } static void client_signal_handler(void) { struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = sig_int; act.sa_flags = SA_RESTART; sigaction(SIGINT, &act, NULL); memset(&act, 0, sizeof(act)); act.sa_handler = sig_int; act.sa_flags = SA_RESTART; sigaction(SIGTERM, &act, NULL); /* Windows uses SIGBREAK as a quit signal from other applications */ #ifdef WIN32 memset(&act, 0, sizeof(act)); act.sa_handler = sig_int; act.sa_flags = SA_RESTART; sigaction(SIGBREAK, &act, NULL); #endif memset(&act, 0, sizeof(act)); act.sa_handler = sig_show_status; act.sa_flags = SA_RESTART; sigaction(SIGUSR1, &act, NULL); } static int send_client_cmd_line(struct fio_client *client) { struct cmd_single_line_pdu *cslp; struct cmd_line_pdu *clp; unsigned long offset; unsigned int *lens; void *pdu; size_t mem; int i, ret; dprint(FD_NET, "client: send cmdline %d\n", client->argc); lens = malloc(client->argc * sizeof(unsigned int)); /* * Find out how much mem we need */ for (i = 0, mem = 0; i < client->argc; i++) { lens[i] = strlen(client->argv[i]) + 1; mem += lens[i]; } /* * We need one cmd_line_pdu, and argc number of cmd_single_line_pdu */ mem += sizeof(*clp) + (client->argc * sizeof(*cslp)); pdu = malloc(mem); clp = pdu; offset = sizeof(*clp); for (i = 0; i < client->argc; i++) { uint16_t arg_len = lens[i]; cslp = pdu + offset; strcpy((char *) cslp->text, client->argv[i]); cslp->len = cpu_to_le16(arg_len); offset += sizeof(*cslp) + arg_len; } free(lens); clp->lines = cpu_to_le16(client->argc); clp->client_type = __cpu_to_le16(client->type); ret = fio_net_send_cmd(client->fd, FIO_NET_CMD_JOBLINE, pdu, mem, NULL, NULL); free(pdu); return ret; } int fio_clients_connect(void) { struct fio_client *client; struct flist_head *entry, *tmp; int ret; #ifdef WIN32 WSADATA wsd; WSAStartup(MAKEWORD(2, 2), &wsd); #endif dprint(FD_NET, "client: connect all\n"); client_signal_handler(); flist_for_each_safe(entry, tmp, &client_list) { client = flist_entry(entry, struct fio_client, list); ret = fio_client_connect(client); if (ret) { remove_client(client); continue; } if (client->argc > 1) send_client_cmd_line(client); } return !nr_clients; } int fio_start_client(struct fio_client *client) { dprint(FD_NET, "client: start %s\n", client->hostname); return fio_net_send_simple_cmd(client->fd, FIO_NET_CMD_RUN, 0, NULL); } int fio_start_all_clients(void) { struct fio_client *client; struct flist_head *entry, *tmp; int ret; dprint(FD_NET, "client: start all\n"); flist_for_each_safe(entry, tmp, &client_list) { client = flist_entry(entry, struct fio_client, list); ret = fio_start_client(client); if (ret) { remove_client(client); continue; } } return flist_empty(&client_list); } /* * Send file contents to server backend. We could use sendfile(), but to remain * more portable lets just read/write the darn thing. */ static int __fio_client_send_ini(struct fio_client *client, const char *filename) { struct cmd_job_pdu *pdu; size_t p_size; struct stat sb; char *p; void *buf; off_t len; int fd, ret; dprint(FD_NET, "send ini %s to %s\n", filename, client->hostname); fd = open(filename, O_RDONLY); if (fd < 0) { int ret = -errno; log_err("fio: job file <%s> open: %s\n", filename, strerror(errno)); return ret; } if (fstat(fd, &sb) < 0) { int ret = -errno; log_err("fio: job file stat: %s\n", strerror(errno)); close(fd); return ret; } p_size = sb.st_size + sizeof(*pdu); pdu = malloc(p_size); buf = pdu->buf; len = sb.st_size; p = buf; do { ret = read(fd, p, len); if (ret > 0) { len -= ret; if (!len) break; p += ret; continue; } else if (!ret) break; else if (errno == EAGAIN || errno == EINTR) continue; } while (1); if (len) { log_err("fio: failed reading job file %s\n", filename); close(fd); free(buf); return 1; } pdu->buf_len = __cpu_to_le32(sb.st_size); pdu->client_type = cpu_to_le32(client->type); client->sent_job = 1; ret = fio_net_send_cmd(client->fd, FIO_NET_CMD_JOB, pdu, p_size, NULL, NULL); free(pdu); close(fd); return ret; } int fio_client_send_ini(struct fio_client *client, const char *filename) { int ret; ret = __fio_client_send_ini(client, filename); if (!ret) client->sent_job = 1; return ret; } int fio_clients_send_ini(const char *filename) { struct fio_client *client; struct flist_head *entry, *tmp; flist_for_each_safe(entry, tmp, &client_list) { client = flist_entry(entry, struct fio_client, list); if (client->nr_ini_file) { int i; for (i = 0; i < client->nr_ini_file; i++) { const char *ini = client->ini_file[i]; if (fio_client_send_ini(client, ini)) { remove_client(client); break; } } } else if (!filename || fio_client_send_ini(client, filename)) remove_client(client); } return !nr_clients; } int fio_client_update_options(struct fio_client *client, struct thread_options *o, uint64_t *tag) { struct cmd_add_job_pdu pdu; pdu.thread_number = cpu_to_le32(client->thread_number); pdu.groupid = cpu_to_le32(client->groupid); convert_thread_options_to_net(&pdu.top, o); return fio_net_send_cmd(client->fd, FIO_NET_CMD_UPDATE_JOB, &pdu, sizeof(pdu), tag, &client->cmd_list); } static void convert_io_stat(struct io_stat *dst, struct io_stat *src) { dst->max_val = le64_to_cpu(src->max_val); dst->min_val = le64_to_cpu(src->min_val); dst->samples = le64_to_cpu(src->samples); /* * Floats arrive as IEEE 754 encoded uint64_t, convert back to double */ dst->mean.u.f = fio_uint64_to_double(le64_to_cpu(dst->mean.u.i)); dst->S.u.f = fio_uint64_to_double(le64_to_cpu(dst->S.u.i)); } static void convert_ts(struct thread_stat *dst, struct thread_stat *src) { int i, j; dst->error = le32_to_cpu(src->error); dst->thread_number = le32_to_cpu(src->thread_number); dst->groupid = le32_to_cpu(src->groupid); dst->pid = le32_to_cpu(src->pid); dst->members = le32_to_cpu(src->members); dst->unified_rw_rep = le32_to_cpu(src->unified_rw_rep); for (i = 0; i < DDIR_RWDIR_CNT; i++) { convert_io_stat(&dst->clat_stat[i], &src->clat_stat[i]); convert_io_stat(&dst->slat_stat[i], &src->slat_stat[i]); convert_io_stat(&dst->lat_stat[i], &src->lat_stat[i]); convert_io_stat(&dst->bw_stat[i], &src->bw_stat[i]); } dst->usr_time = le64_to_cpu(src->usr_time); dst->sys_time = le64_to_cpu(src->sys_time); dst->ctx = le64_to_cpu(src->ctx); dst->minf = le64_to_cpu(src->minf); dst->majf = le64_to_cpu(src->majf); dst->clat_percentiles = le64_to_cpu(src->clat_percentiles); for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) { fio_fp64_t *fps = &src->percentile_list[i]; fio_fp64_t *fpd = &dst->percentile_list[i]; fpd->u.f = fio_uint64_to_double(le64_to_cpu(fps->u.i)); } for (i = 0; i < FIO_IO_U_MAP_NR; i++) { dst->io_u_map[i] = le32_to_cpu(src->io_u_map[i]); dst->io_u_submit[i] = le32_to_cpu(src->io_u_submit[i]); dst->io_u_complete[i] = le32_to_cpu(src->io_u_complete[i]); } for (i = 0; i < FIO_IO_U_LAT_U_NR; i++) { dst->io_u_lat_u[i] = le32_to_cpu(src->io_u_lat_u[i]); dst->io_u_lat_m[i] = le32_to_cpu(src->io_u_lat_m[i]); } for (i = 0; i < DDIR_RWDIR_CNT; i++) for (j = 0; j < FIO_IO_U_PLAT_NR; j++) dst->io_u_plat[i][j] = le32_to_cpu(src->io_u_plat[i][j]); for (i = 0; i < DDIR_RWDIR_CNT; i++) { dst->total_io_u[i] = le64_to_cpu(src->total_io_u[i]); dst->short_io_u[i] = le64_to_cpu(src->short_io_u[i]); } dst->total_submit = le64_to_cpu(src->total_submit); dst->total_complete = le64_to_cpu(src->total_complete); for (i = 0; i < DDIR_RWDIR_CNT; i++) { dst->io_bytes[i] = le64_to_cpu(src->io_bytes[i]); dst->runtime[i] = le64_to_cpu(src->runtime[i]); } dst->total_run_time = le64_to_cpu(src->total_run_time); dst->continue_on_error = le16_to_cpu(src->continue_on_error); dst->total_err_count = le64_to_cpu(src->total_err_count); dst->first_error = le32_to_cpu(src->first_error); dst->kb_base = le32_to_cpu(src->kb_base); dst->unit_base = le32_to_cpu(src->unit_base); } static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src) { int i; for (i = 0; i < DDIR_RWDIR_CNT; i++) { dst->max_run[i] = le64_to_cpu(src->max_run[i]); dst->min_run[i] = le64_to_cpu(src->min_run[i]); dst->max_bw[i] = le64_to_cpu(src->max_bw[i]); dst->min_bw[i] = le64_to_cpu(src->min_bw[i]); dst->io_kb[i] = le64_to_cpu(src->io_kb[i]); dst->agg[i] = le64_to_cpu(src->agg[i]); } dst->kb_base = le32_to_cpu(src->kb_base); dst->unit_base = le32_to_cpu(src->unit_base); dst->groupid = le32_to_cpu(src->groupid); dst->unified_rw_rep = le32_to_cpu(src->unified_rw_rep); } static void handle_ts(struct fio_client *client, struct fio_net_cmd *cmd) { struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload; show_thread_status(&p->ts, &p->rs); client->did_stat = 1; if (!do_output_all_clients) return; sum_thread_stats(&client_ts, &p->ts, sum_stat_nr); sum_group_stats(&client_gs, &p->rs); client_ts.members++; client_ts.thread_number = p->ts.thread_number; client_ts.groupid = p->ts.groupid; client_ts.unified_rw_rep = p->ts.unified_rw_rep; if (++sum_stat_nr == sum_stat_clients) { strcpy(client_ts.name, "All clients"); show_thread_status(&client_ts, &client_gs); } } static void handle_gs(struct fio_client *client, struct fio_net_cmd *cmd) { struct group_run_stats *gs = (struct group_run_stats *) cmd->payload; show_group_stats(gs); } static void handle_text(struct fio_client *client, struct fio_net_cmd *cmd) { struct cmd_text_pdu *pdu = (struct cmd_text_pdu *) cmd->payload; const char *buf = (const char *) pdu->buf; const char *name; int fio_unused ret; name = client->name ? client->name : client->hostname; if (!client->skip_newline) fprintf(f_out, "<%s> ", name); ret = fwrite(buf, pdu->buf_len, 1, f_out); fflush(f_out); client->skip_newline = strchr(buf, '\n') == NULL; } static void convert_agg(struct disk_util_agg *agg) { int i; for (i = 0; i < 2; i++) { agg->ios[i] = le32_to_cpu(agg->ios[i]); agg->merges[i] = le32_to_cpu(agg->merges[i]); agg->sectors[i] = le64_to_cpu(agg->sectors[i]); agg->ticks[i] = le32_to_cpu(agg->ticks[i]); } agg->io_ticks = le32_to_cpu(agg->io_ticks); agg->time_in_queue = le32_to_cpu(agg->time_in_queue); agg->slavecount = le32_to_cpu(agg->slavecount); agg->max_util.u.f = fio_uint64_to_double(__le64_to_cpu(agg->max_util.u.i)); } static void convert_dus(struct disk_util_stat *dus) { int i; for (i = 0; i < 2; i++) { dus->ios[i] = le32_to_cpu(dus->ios[i]); dus->merges[i] = le32_to_cpu(dus->merges[i]); dus->sectors[i] = le64_to_cpu(dus->sectors[i]); dus->ticks[i] = le32_to_cpu(dus->ticks[i]); } dus->io_ticks = le32_to_cpu(dus->io_ticks); dus->time_in_queue = le32_to_cpu(dus->time_in_queue); dus->msec = le64_to_cpu(dus->msec); } static void handle_du(struct fio_client *client, struct fio_net_cmd *cmd) { struct cmd_du_pdu *du = (struct cmd_du_pdu *) cmd->payload; if (!client->disk_stats_shown) { client->disk_stats_shown = 1; log_info("\nDisk stats (read/write):\n"); } print_disk_util(&du->dus, &du->agg, output_format == FIO_OUTPUT_TERSE); } static void convert_jobs_eta(struct jobs_eta *je) { int i; je->nr_running = le32_to_cpu(je->nr_running); je->nr_ramp = le32_to_cpu(je->nr_ramp); je->nr_pending = le32_to_cpu(je->nr_pending); je->nr_setting_up = le32_to_cpu(je->nr_setting_up); je->files_open = le32_to_cpu(je->files_open); for (i = 0; i < DDIR_RWDIR_CNT; i++) { je->m_rate[i] = le32_to_cpu(je->m_rate[i]); je->t_rate[i] = le32_to_cpu(je->t_rate[i]); je->m_iops[i] = le32_to_cpu(je->m_iops[i]); je->t_iops[i] = le32_to_cpu(je->t_iops[i]); je->rate[i] = le32_to_cpu(je->rate[i]); je->iops[i] = le32_to_cpu(je->iops[i]); } je->elapsed_sec = le64_to_cpu(je->elapsed_sec); je->eta_sec = le64_to_cpu(je->eta_sec); je->nr_threads = le32_to_cpu(je->nr_threads); je->is_pow2 = le32_to_cpu(je->is_pow2); je->unit_base = le32_to_cpu(je->unit_base); } void fio_client_sum_jobs_eta(struct jobs_eta *dst, struct jobs_eta *je) { int i; dst->nr_running += je->nr_running; dst->nr_ramp += je->nr_ramp; dst->nr_pending += je->nr_pending; dst->nr_setting_up += je->nr_setting_up; dst->files_open += je->files_open; for (i = 0; i < DDIR_RWDIR_CNT; i++) { dst->m_rate[i] += je->m_rate[i]; dst->t_rate[i] += je->t_rate[i]; dst->m_iops[i] += je->m_iops[i]; dst->t_iops[i] += je->t_iops[i]; dst->rate[i] += je->rate[i]; dst->iops[i] += je->iops[i]; } dst->elapsed_sec += je->elapsed_sec; if (je->eta_sec > dst->eta_sec) dst->eta_sec = je->eta_sec; dst->nr_threads += je->nr_threads; /* we need to handle je->run_str too ... */ } void fio_client_dec_jobs_eta(struct client_eta *eta, client_eta_op eta_fn) { if (!--eta->pending) { eta_fn(&eta->eta); free(eta); } } static void remove_reply_cmd(struct fio_client *client, struct fio_net_cmd *cmd) { struct fio_net_cmd_reply *reply = NULL; struct flist_head *entry; flist_for_each(entry, &client->cmd_list) { reply = flist_entry(entry, struct fio_net_cmd_reply, list); if (cmd->tag == (uintptr_t) reply) break; reply = NULL; } if (!reply) { log_err("fio: client: unable to find matching tag (%llx)\n", (unsigned long long) cmd->tag); return; } flist_del(&reply->list); cmd->tag = reply->saved_tag; free(reply); } int fio_client_wait_for_reply(struct fio_client *client, uint64_t tag) { do { struct fio_net_cmd_reply *reply = NULL; struct flist_head *entry; flist_for_each(entry, &client->cmd_list) { reply = flist_entry(entry, struct fio_net_cmd_reply, list); if (tag == (uintptr_t) reply) break; reply = NULL; } if (!reply) break; usleep(1000); } while (1); return 0; } static void handle_eta(struct fio_client *client, struct fio_net_cmd *cmd) { struct jobs_eta *je = (struct jobs_eta *) cmd->payload; struct client_eta *eta = (struct client_eta *) (uintptr_t) cmd->tag; dprint(FD_NET, "client: got eta tag %p, %d\n", eta, eta->pending); assert(client->eta_in_flight == eta); client->eta_in_flight = NULL; flist_del_init(&client->eta_list); if (client->ops->jobs_eta) client->ops->jobs_eta(client, je); fio_client_sum_jobs_eta(&eta->eta, je); fio_client_dec_jobs_eta(eta, client->ops->eta); } static void handle_probe(struct fio_client *client, struct fio_net_cmd *cmd) { struct cmd_probe_reply_pdu *probe = (struct cmd_probe_reply_pdu *) cmd->payload; const char *os, *arch; char bit[16]; os = fio_get_os_string(probe->os); if (!os) os = "unknown"; arch = fio_get_arch_string(probe->arch); if (!arch) os = "unknown"; sprintf(bit, "%d-bit", probe->bpp * 8); probe->flags = le64_to_cpu(probe->flags); log_info("hostname=%s, be=%u, %s, os=%s, arch=%s, fio=%s, flags=%lx\n", probe->hostname, probe->bigendian, bit, os, arch, probe->fio_version, (unsigned long) probe->flags); if (!client->name) client->name = strdup((char *) probe->hostname); } static void handle_start(struct fio_client *client, struct fio_net_cmd *cmd) { struct cmd_start_pdu *pdu = (struct cmd_start_pdu *) cmd->payload; client->state = Client_started; client->jobs = le32_to_cpu(pdu->jobs); client->nr_stat = le32_to_cpu(pdu->stat_outputs); if (sum_stat_clients > 1) do_output_all_clients = 1; sum_stat_clients += client->nr_stat; } static void handle_stop(struct fio_client *client, struct fio_net_cmd *cmd) { if (client->error) log_info("client <%s>: exited with error %d\n", client->hostname, client->error); } static void convert_stop(struct fio_net_cmd *cmd) { struct cmd_end_pdu *pdu = (struct cmd_end_pdu *) cmd->payload; pdu->error = le32_to_cpu(pdu->error); } static void convert_text(struct fio_net_cmd *cmd) { struct cmd_text_pdu *pdu = (struct cmd_text_pdu *) cmd->payload; pdu->level = le32_to_cpu(pdu->level); pdu->buf_len = le32_to_cpu(pdu->buf_len); pdu->log_sec = le64_to_cpu(pdu->log_sec); pdu->log_usec = le64_to_cpu(pdu->log_usec); } static struct cmd_iolog_pdu *convert_iolog_gz(struct fio_net_cmd *cmd, struct cmd_iolog_pdu *pdu) { #ifdef CONFIG_ZLIB struct cmd_iolog_pdu *ret; z_stream stream; uint32_t nr_samples; size_t total; void *p; stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; stream.avail_in = 0; stream.next_in = Z_NULL; if (inflateInit(&stream) != Z_OK) return NULL; /* * Get header first, it's not compressed */ nr_samples = le32_to_cpu(pdu->nr_samples); total = nr_samples * sizeof(struct io_sample); ret = malloc(total + sizeof(*pdu)); ret->nr_samples = nr_samples; memcpy(ret, pdu, sizeof(*pdu)); p = (void *) ret + sizeof(*pdu); stream.avail_in = cmd->pdu_len - sizeof(*pdu); stream.next_in = (void *) pdu + sizeof(*pdu); while (stream.avail_in) { unsigned int this_chunk = 65536; unsigned int this_len; int err; if (this_chunk > total) this_chunk = total; stream.avail_out = this_chunk; stream.next_out = p; err = inflate(&stream, Z_NO_FLUSH); /* may be Z_OK, or Z_STREAM_END */ if (err < 0) { log_err("fio: inflate error %d\n", err); free(ret); ret = NULL; goto err; } this_len = this_chunk - stream.avail_out; p += this_len; total -= this_len; } err: inflateEnd(&stream); return ret; #else return NULL; #endif } /* * This has been compressed on the server side, since it can be big. * Uncompress here. */ static struct cmd_iolog_pdu *convert_iolog(struct fio_net_cmd *cmd) { struct cmd_iolog_pdu *pdu = (struct cmd_iolog_pdu *) cmd->payload; struct cmd_iolog_pdu *ret; int i; /* * Convert if compressed and we support it. If it's not * compressed, we need not do anything. */ if (le32_to_cpu(pdu->compressed)) { #ifndef CONFIG_ZLIB log_err("fio: server sent compressed data by mistake\n"); return NULL; #endif ret = convert_iolog_gz(cmd, pdu); if (!ret) { log_err("fio: failed decompressing log\n"); return NULL; } } else ret = pdu; ret->thread_number = le32_to_cpu(ret->thread_number); ret->nr_samples = le32_to_cpu(ret->nr_samples); ret->log_type = le32_to_cpu(ret->log_type); ret->compressed = le32_to_cpu(ret->compressed); for (i = 0; i < ret->nr_samples; i++) { struct io_sample *s = &ret->samples[i]; s->time = le64_to_cpu(s->time); s->val = le64_to_cpu(s->val); s->ddir = le32_to_cpu(s->ddir); s->bs = le32_to_cpu(s->bs); } return ret; } int fio_handle_client(struct fio_client *client) { struct client_ops *ops = client->ops; struct fio_net_cmd *cmd; dprint(FD_NET, "client: handle %s\n", client->hostname); cmd = fio_net_recv_cmd(client->fd); if (!cmd) return 0; dprint(FD_NET, "client: got cmd op %s from %s (pdu=%u)\n", fio_server_op(cmd->opcode), client->hostname, cmd->pdu_len); switch (cmd->opcode) { case FIO_NET_CMD_QUIT: if (ops->quit) ops->quit(client, cmd); remove_client(client); free(cmd); break; case FIO_NET_CMD_TEXT: convert_text(cmd); ops->text(client, cmd); free(cmd); break; case FIO_NET_CMD_DU: { struct cmd_du_pdu *du = (struct cmd_du_pdu *) cmd->payload; convert_dus(&du->dus); convert_agg(&du->agg); ops->disk_util(client, cmd); free(cmd); break; } case FIO_NET_CMD_TS: { struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload; convert_ts(&p->ts, &p->ts); convert_gs(&p->rs, &p->rs); ops->thread_status(client, cmd); free(cmd); break; } case FIO_NET_CMD_GS: { struct group_run_stats *gs = (struct group_run_stats *) cmd->payload; convert_gs(gs, gs); ops->group_stats(client, cmd); free(cmd); break; } case FIO_NET_CMD_ETA: { struct jobs_eta *je = (struct jobs_eta *) cmd->payload; remove_reply_cmd(client, cmd); convert_jobs_eta(je); handle_eta(client, cmd); free(cmd); break; } case FIO_NET_CMD_PROBE: remove_reply_cmd(client, cmd); ops->probe(client, cmd); free(cmd); break; case FIO_NET_CMD_SERVER_START: client->state = Client_running; if (ops->job_start) ops->job_start(client, cmd); free(cmd); break; case FIO_NET_CMD_START: { struct cmd_start_pdu *pdu = (struct cmd_start_pdu *) cmd->payload; pdu->jobs = le32_to_cpu(pdu->jobs); ops->start(client, cmd); free(cmd); break; } case FIO_NET_CMD_STOP: { struct cmd_end_pdu *pdu = (struct cmd_end_pdu *) cmd->payload; convert_stop(cmd); client->state = Client_stopped; client->error = le32_to_cpu(pdu->error); client->signal = le32_to_cpu(pdu->signal); ops->stop(client, cmd); free(cmd); break; } case FIO_NET_CMD_ADD_JOB: { struct cmd_add_job_pdu *pdu = (struct cmd_add_job_pdu *) cmd->payload; client->thread_number = le32_to_cpu(pdu->thread_number); client->groupid = le32_to_cpu(pdu->groupid); if (ops->add_job) ops->add_job(client, cmd); free(cmd); break; } case FIO_NET_CMD_IOLOG: if (ops->iolog) { struct cmd_iolog_pdu *pdu; pdu = convert_iolog(cmd); ops->iolog(client, pdu); } free(cmd); break; case FIO_NET_CMD_UPDATE_JOB: ops->update_job(client, cmd); remove_reply_cmd(client, cmd); free(cmd); break; default: log_err("fio: unknown client op: %s\n", fio_server_op(cmd->opcode)); free(cmd); break; } return 1; } static void request_client_etas(struct client_ops *ops) { struct fio_client *client; struct flist_head *entry; struct client_eta *eta; int skipped = 0; dprint(FD_NET, "client: request eta (%d)\n", nr_clients); eta = malloc(sizeof(*eta)); memset(&eta->eta, 0, sizeof(eta->eta)); eta->pending = nr_clients; flist_for_each(entry, &client_list) { client = flist_entry(entry, struct fio_client, list); if (!flist_empty(&client->eta_list)) { skipped++; continue; } if (client->state != Client_running) continue; assert(!client->eta_in_flight); flist_add_tail(&client->eta_list, &eta_list); client->eta_in_flight = eta; fio_net_send_simple_cmd(client->fd, FIO_NET_CMD_SEND_ETA, (uintptr_t) eta, &client->cmd_list); } while (skipped--) fio_client_dec_jobs_eta(eta, ops->eta); dprint(FD_NET, "client: requested eta tag %p\n", eta); } static int client_check_cmd_timeout(struct fio_client *client, struct timeval *now) { struct fio_net_cmd_reply *reply; struct flist_head *entry, *tmp; int ret = 0; flist_for_each_safe(entry, tmp, &client->cmd_list) { reply = flist_entry(entry, struct fio_net_cmd_reply, list); if (mtime_since(&reply->tv, now) < FIO_NET_CLIENT_TIMEOUT) continue; log_err("fio: client %s, timeout on cmd %s\n", client->hostname, fio_server_op(reply->opcode)); flist_del(&reply->list); free(reply); ret = 1; } return flist_empty(&client->cmd_list) && ret; } static int fio_check_clients_timed_out(void) { struct fio_client *client; struct flist_head *entry, *tmp; struct timeval tv; int ret = 0; fio_gettime(&tv, NULL); flist_for_each_safe(entry, tmp, &client_list) { client = flist_entry(entry, struct fio_client, list); if (flist_empty(&client->cmd_list)) continue; if (!client_check_cmd_timeout(client, &tv)) continue; if (client->ops->timed_out) client->ops->timed_out(client); else log_err("fio: client %s timed out\n", client->hostname); remove_client(client); ret = 1; } return ret; } int fio_handle_clients(struct client_ops *ops) { struct pollfd *pfds; int i, ret = 0, retval = 0; fio_gettime(&eta_tv, NULL); pfds = malloc(nr_clients * sizeof(struct pollfd)); init_thread_stat(&client_ts); init_group_run_stat(&client_gs); while (!exit_backend && nr_clients) { struct flist_head *entry, *tmp; struct fio_client *client; i = 0; flist_for_each_safe(entry, tmp, &client_list) { client = flist_entry(entry, struct fio_client, list); if (!client->sent_job && !client->ops->stay_connected && flist_empty(&client->cmd_list)) { remove_client(client); continue; } pfds[i].fd = client->fd; pfds[i].events = POLLIN; i++; } if (!nr_clients) break; assert(i == nr_clients); do { struct timeval tv; fio_gettime(&tv, NULL); if (mtime_since(&eta_tv, &tv) >= 900) { request_client_etas(ops); memcpy(&eta_tv, &tv, sizeof(tv)); if (fio_check_clients_timed_out()) break; } ret = poll(pfds, nr_clients, ops->eta_msec); if (ret < 0) { if (errno == EINTR) continue; log_err("fio: poll clients: %s\n", strerror(errno)); break; } else if (!ret) continue; } while (ret <= 0); for (i = 0; i < nr_clients; i++) { if (!(pfds[i].revents & POLLIN)) continue; client = find_client_by_fd(pfds[i].fd); if (!client) { log_err("fio: unknown client fd %ld\n", (long) pfds[i].fd); continue; } if (!fio_handle_client(client)) { log_info("client: host=%s disconnected\n", client->hostname); remove_client(client); retval = 1; } else if (client->error) retval = 1; fio_put_client(client); } } free(pfds); return retval; } fio-2.1.3/client.h000066400000000000000000000066171222032232000137170ustar00rootroot00000000000000#ifndef CLIENT_H #define CLIENT_H #include #include #include #include #include "stat.h" struct fio_net_cmd; struct client_ops; enum { Client_created = 0, Client_connected = 1, Client_started = 2, Client_running = 3, Client_stopped = 4, Client_exited = 5, }; struct fio_client { struct flist_head list; struct flist_head hash_list; struct flist_head arg_list; union { struct sockaddr_in addr; struct sockaddr_in6 addr6; struct sockaddr_un addr_un; }; char *hostname; int port; int fd; unsigned int refs; char *name; int state; int skip_newline; int is_sock; int disk_stats_shown; unsigned int jobs; unsigned int nr_stat; int error; int signal; int ipv6; int sent_job; int did_stat; uint32_t type; uint32_t thread_number; uint32_t groupid; struct flist_head eta_list; struct client_eta *eta_in_flight; struct flist_head cmd_list; uint16_t argc; char **argv; struct client_ops *ops; void *client_data; char **ini_file; unsigned int nr_ini_file; }; struct cmd_iolog_pdu; typedef void (client_cmd_op)(struct fio_client *, struct fio_net_cmd *); typedef void (client_eta_op)(struct jobs_eta *je); typedef void (client_timed_out_op)(struct fio_client *); typedef void (client_jobs_eta_op)(struct fio_client *client, struct jobs_eta *je); typedef void (client_iolog_op)(struct fio_client *client, struct cmd_iolog_pdu *); struct client_ops { client_cmd_op *text; client_cmd_op *disk_util; client_cmd_op *thread_status; client_cmd_op *group_stats; client_jobs_eta_op *jobs_eta; client_eta_op *eta; client_cmd_op *probe; client_cmd_op *quit; client_cmd_op *add_job; client_cmd_op *update_job; client_timed_out_op *timed_out; client_cmd_op *stop; client_cmd_op *start; client_cmd_op *job_start; client_iolog_op *iolog; client_timed_out_op *removed; unsigned int eta_msec; int stay_connected; uint32_t client_type; }; extern struct client_ops fio_client_ops; struct client_eta { unsigned int pending; struct jobs_eta eta; }; extern int fio_handle_client(struct fio_client *); extern void fio_client_dec_jobs_eta(struct client_eta *eta, client_eta_op fn); extern void fio_client_sum_jobs_eta(struct jobs_eta *dst, struct jobs_eta *je); enum { Fio_client_ipv4 = 1, Fio_client_ipv6, Fio_client_socket, }; extern int fio_client_connect(struct fio_client *); extern int fio_clients_connect(void); extern int fio_start_client(struct fio_client *); extern int fio_start_all_clients(void); extern int fio_client_send_ini(struct fio_client *, const char *); extern int fio_clients_send_ini(const char *); extern int fio_handle_clients(struct client_ops *); extern int fio_client_add(struct client_ops *, const char *, void **); extern struct fio_client *fio_client_add_explicit(struct client_ops *, const char *, int, int); extern void fio_client_add_cmd_option(void *, const char *); extern void fio_client_add_ini_file(void *, const char *); extern int fio_client_terminate(struct fio_client *); extern void fio_clients_terminate(void); extern struct fio_client *fio_get_client(struct fio_client *); extern void fio_put_client(struct fio_client *); extern int fio_client_update_options(struct fio_client *, struct thread_options *, uint64_t *); extern int fio_client_wait_for_reply(struct fio_client *, uint64_t); #define FIO_CLIENT_DEF_ETA_MSEC 900 enum { FIO_CLIENT_TYPE_CLI = 1, FIO_CLIENT_TYPE_GUI = 2, }; #endif fio-2.1.3/compiler/000077500000000000000000000000001222032232000140705ustar00rootroot00000000000000fio-2.1.3/compiler/compiler-gcc3.h000066400000000000000000000002661222032232000166740ustar00rootroot00000000000000#ifndef FIO_COMPILER_GCC3_H #define FIO_COMPILER_GCC3_H #if __GNUC_MINOR__ >= 4 #ifndef __must_check #define __must_check __attribute__((warn_unused_result)) #endif #endif #endif fio-2.1.3/compiler/compiler-gcc4.h000066400000000000000000000002271222032232000166720ustar00rootroot00000000000000#ifndef FIO_COMPILER_GCC4_H #define FIO_COMPILER_GCC4_H #ifndef __must_check #define __must_check __attribute__((warn_unused_result)) #endif #endif fio-2.1.3/compiler/compiler.h000066400000000000000000000007421222032232000160560ustar00rootroot00000000000000#ifndef FIO_COMPILER_H #define FIO_COMPILER_H #if __GNUC__ >= 4 #include "compiler-gcc4.h" #elif __GNUC__ == 3 #include "compiler-gcc3.h" #else #error Compiler too old, need gcc at least gcc 3.x #endif #ifndef __must_check #define __must_check #endif /* * Mark unused variables passed to ops functions as unused, to silence gcc */ #define fio_unused __attribute__((__unused__)) #define fio_init __attribute__((constructor)) #define fio_exit __attribute__((destructor)) #endif fio-2.1.3/configure000077500000000000000000000656271222032232000142050ustar00rootroot00000000000000#!/bin/sh # # Fio configure script. Heavily influenced by the manual qemu configure # script. Sad this this is easier than autoconf and enemies. # # set temporary file name if test ! -z "$TMPDIR" ; then TMPDIR1="${TMPDIR}" elif test ! -z "$TEMPDIR" ; then TMPDIR1="${TEMPDIR}" else TMPDIR1="/tmp" fi TMPC="${TMPDIR1}/fio-conf-${RANDOM}-$$-${RANDOM}.c" TMPO="${TMPDIR1}/fio-conf-${RANDOM}-$$-${RANDOM}.o" TMPE="${TMPDIR1}/fio-conf-${RANDOM}-$$-${RANDOM}.exe" # NB: do not call "exit" in the trap handler; this is buggy with some shells; # see <1285349658-3122-1-git-send-email-loic.minier@linaro.org> trap "rm -f $TMPC $TMPO $TMPE" EXIT INT QUIT TERM rm -rf config.log config_host_mak="config-host.mak" config_host_h="config-host.h" rm -rf $config_host_mak rm -rf $config_host_h fatal() { echo $@ echo "Configure failed, check config.log and/or the above output" rm -rf $config_host_mak rm -rf $config_host_h exit 1 } # Default CFLAGS CFLAGS="-D_GNU_SOURCE -include config-host.h" BUILD_CFLAGS="" # Print a helpful header at the top of config.log echo "# FIO configure log $(date)" >> config.log printf "# Configured with:" >> config.log printf " '%s'" "$0" "$@" >> config.log echo >> config.log echo "#" >> config.log # Print configure header at the top of $config_host_h echo "/*" > $config_host_h echo " * Automatically generated by configure - do not modify" >> $config_host_h printf " * Configured with:" >> $config_host_h printf " * '%s'" "$0" "$@" >> $config_host_h echo "" >> $config_host_h echo " */" >> $config_host_h do_cc() { # Run the compiler, capturing its output to the log. echo $cc "$@" >> config.log $cc "$@" >> config.log 2>&1 || return $? # Test passed. If this is an --enable-werror build, rerun # the test with -Werror and bail out if it fails. This # makes warning-generating-errors in configure test code # obvious to developers. if test "$werror" != "yes"; then return 0 fi # Don't bother rerunning the compile if we were already using -Werror case "$*" in *-Werror*) return 0 ;; esac echo $cc -Werror "$@" >> config.log $cc -Werror "$@" >> config.log 2>&1 && return $? echo "ERROR: configure test passed without -Werror but failed with -Werror." echo "This is probably a bug in the configure script. The failing command" echo "will be at the bottom of config.log." fatal "You can run configure with --disable-werror to bypass this check." } compile_object() { do_cc $CFLAGS -c -o $TMPO $TMPC } compile_prog() { local_cflags="$1" local_ldflags="$2 $LIBS" echo "Compiling test case $3" >> config.log do_cc $CFLAGS $local_cflags -o $TMPE $TMPC $LDFLAGS $local_ldflags } feature_not_found() { feature=$1 packages=$2 echo "ERROR" echo "ERROR: User requested feature $feature" if test ! -z "$packages" ; then echo "ERROR: That feature needs $packages installed" fi echo "ERROR: configure was not able to find it" fatal "ERROR" } has() { type "$1" >/dev/null 2>&1 } check_define() { cat > $TMPC <> $config_host_mak echo "#define $1" >> $config_host_h } targetos="" cpu="" cross_prefix=${cross_prefix-${CROSS_COMPILE}} cc="${CC-${cross_prefix}gcc}" # default options show_help="no" exit_val=0 gfio="no" # parse options for opt do optarg=`expr "x$opt" : 'x[^=]*=\(.*\)'` case "$opt" in --cpu=*) cpu="$optarg" ;; --cc=*) CC="$optarg" ;; --extra-cflags=*) CFLAGS="$CFLAGS $optarg" ;; --build-32bit-win) build_32bit_win="yes" ;; --enable-gfio) gfio="yes" ;; --help) show_help="yes" ;; *) echo "Bad option $opt" show_help="yes" exit_val=1 esac done if test "$show_help" = "yes" ; then echo "--cpu= Specify target CPU if auto-detect fails" echo "--cc= Specify compiler to use" echo "--extra-cflags= Specify extra CFLAGS to pass to compiler" echo "--build-32bit-win Enable 32-bit build on Windows" echo "--enable-gfio Enable building of gtk gfio" exit $exit_val fi if check_define __ANDROID__ ; then targetos="Android" elif check_define __linux__ ; then targetos="Linux" elif check_define __OpenBSD__ ; then targetos='OpenBSD' elif check_define __sun__ ; then targetos='SunOS' else targetos=`uname -s` fi echo "# Automatically generated by configure - do not modify" > $config_host_mak printf "# Configured with:" >> $config_host_mak printf " '%s'" "$0" "$@" >> $config_host_mak echo >> $config_host_mak echo "CONFIG_TARGET_OS=$targetos" >> $config_host_mak # Some host OSes need non-standard checks for which CPU to use. # Note that these checks are broken for cross-compilation: if you're # cross-compiling to one of these OSes then you'll need to specify # the correct CPU with the --cpu option. case $targetos in Darwin) # on Leopard most of the system is 32-bit, so we have to ask the kernel if # we can run 64-bit userspace code. # If the user didn't specify a CPU explicitly and the kernel says this is # 64 bit hw, then assume x86_64. Otherwise fall through to the usual # detection code. if test -z "$cpu" && test "$(sysctl -n hw.optional.x86_64)" = "1"; then cpu="x86_64" fi ;; SunOS) # `uname -m` returns i86pc even on an x86_64 box, so default based on isainfo if test -z "$cpu" && test "$(isainfo -k)" = "amd64"; then cpu="x86_64" fi LIBS="-lnsl -lsocket" ;; CYGWIN*) echo "Forcing known good options on Windows" if test -z "$CC" ; then if test ! -z "$build_32bit_win" && test "$build_32bit_win" = "yes"; then CC="i686-w64-mingw32-gcc" else CC="x86_64-w64-mingw32-gcc" fi fi output_sym "CONFIG_LITTLE_ENDIAN" if test ! -z "$build_32bit_win" && test "$build_32bit_win" = "yes"; then output_sym "CONFIG_32BIT" else output_sym "CONFIG_64BIT_LLP64" fi output_sym "CONFIG_FADVISE" output_sym "CONFIG_SOCKLEN_T" output_sym "CONFIG_FADVISE" output_sym "CONFIG_SFAA" output_sym "CONFIG_RUSAGE_THREAD" output_sym "CONFIG_WINDOWSAIO" output_sym "CONFIG_FDATASYNC" output_sym "CONFIG_CLOCK_MONOTONIC" output_sym "CONFIG_GETTIMEOFDAY" output_sym "CONFIG_CLOCK_GETTIME" output_sym "CONFIG_SCHED_IDLE" output_sym "CONFIG_TCP_NODELAY" echo "CC=$CC" >> $config_host_mak echo "BUILD_CFLAGS=$CFLAGS -include config-host.h -D_GNU_SOURCE" >> $config_host_mak exit 0 ;; esac if test ! -z "$cpu" ; then # command line argument : elif check_define __i386__ ; then cpu="i386" elif check_define __x86_64__ ; then cpu="x86_64" elif check_define __sparc__ ; then if check_define __arch64__ ; then cpu="sparc64" else cpu="sparc" fi elif check_define _ARCH_PPC ; then if check_define _ARCH_PPC64 ; then cpu="ppc64" else cpu="ppc" fi elif check_define __mips__ ; then cpu="mips" elif check_define __ia64__ ; then cpu="ia64" elif check_define __s390__ ; then if check_define __s390x__ ; then cpu="s390x" else cpu="s390" fi elif check_define __arm__ ; then cpu="arm" elif check_define __hppa__ ; then cpu="hppa" else cpu=`uname -m` fi # Normalise host CPU name and set ARCH. case "$cpu" in ia64|ppc|ppc64|s390|s390x|sparc64) cpu="$cpu" ;; i386|i486|i586|i686|i86pc|BePC) cpu="i386" ;; x86_64|amd64) cpu="x86_64" ;; armv*b|armv*l|arm) cpu="arm" ;; hppa|parisc|parisc64) cpu="hppa" ;; mips*) cpu="mips" ;; sparc|sun4[cdmuv]) cpu="sparc" ;; *) echo "Unknown CPU" ;; esac if test -z "$CC" ; then if test "$targetos" = "FreeBSD"; then if has clang; then CC=clang else CC=gcc fi fi fi cc="${CC-${cross_prefix}gcc}" ########################################## # check cross compile cross_compile="no" cat > $TMPC </dev/null || cross_compile="yes" else fatal "compile test failed" fi ########################################## # check endianness bigendian="no" if test "$cross_compile" = "no" ; then cat > $TMPC < int main(void) { volatile uint32_t i=0x01234567; return (*((uint8_t*)(&i))) == 0x67; } EOF if compile_prog "" "" "endian"; then $TMPE && bigendian="yes" fi else # If we're cross compiling, try our best to work it out and rely on the # run-time check to fail if we get it wrong. cat > $TMPC < int main(void) { #if __BYTE_ORDER != __BIG_ENDIAN # error "Unknown endianness" #endif } EOF compile_prog "" "" "endian" && bigendian="yes" check_define "__ARMEB__" && bigendian="yes" check_define "__MIPSEB__" && bigendian="yes" fi echo "Operating system $targetos" echo "CPU $cpu" echo "Big endian $bigendian" echo "Compiler $cc" echo "Cross compile $cross_compile" echo ########################################## # check for wordsize wordsize="0" cat > $TMPC < #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) int main(void) { BUILD_BUG_ON(sizeof(long)*CHAR_BIT != WORDSIZE); return 0; } EOF if compile_prog "-DWORDSIZE=32" "" "wordsize"; then wordsize="32" elif compile_prog "-DWORDSIZE=64" "" "wordsize"; then wordsize="64" else fatal "Unknown wordsize" fi echo "Wordsize $wordsize" ########################################## # zlib probe zlib="no" cat > $TMPC < int main(void) { z_stream stream; if (inflateInit(&stream) != Z_OK) return 1; return 0; } EOF if compile_prog "" "-lz" "zlib" ; then zlib=yes LIBS="-lz $LIBS" fi echo "zlib $zlib" ########################################## # linux-aio probe libaio="no" cat > $TMPC < #include int main(void) { io_setup(0, NULL); return 0; } EOF if compile_prog "" "-laio" "libaio" ; then libaio=yes LIBS="-laio $LIBS" else if test "$libaio" = "yes" ; then feature_not_found "linux AIO" "libaio-dev or libaio-devel" fi libaio=no fi echo "Linux AIO support $libaio" ########################################## # posix aio probe posix_aio="no" posix_aio_lrt="no" cat > $TMPC < int main(void) { struct aiocb cb; aio_read(&cb); return 0; } EOF if compile_prog "" "" "posixaio" ; then posix_aio="yes" elif compile_prog "" "-lrt" "posixaio"; then posix_aio="yes" posix_aio_lrt="yes" LIBS="-lrt $LIBS" fi echo "POSIX AIO support $posix_aio" echo "POSIX AIO support needs -lrt $posix_aio_lrt" ########################################## # posix aio fsync probe posix_aio_fsync="no" if test "$posix_aio" = "yes" ; then cat > $TMPC < #include int main(void) { struct aiocb cb; return aio_fsync(O_SYNC, &cb); return 0; } EOF if compile_prog "" "$LIBS" "posix_aio_fsync" ; then posix_aio_fsync=yes fi fi echo "POSIX AIO fsync $posix_aio_fsync" ########################################## # solaris aio probe solaris_aio="no" cat > $TMPC < #include #include int main(void) { aio_result_t res; return aioread(0, NULL, 0, 0, SEEK_SET, &res); return 0; } EOF if compile_prog "" "-laio" "solarisaio" ; then solaris_aio=yes LIBS="-laio $LIBS" fi echo "Solaris AIO support $solaris_aio" ########################################## # __sync_fetch_and_and test sfaa="no" cat > $TMPC << EOF static int sfaa(int *ptr) { return __sync_fetch_and_add(ptr, 0); } int main(int argc, char **argv) { int val = 42; sfaa(&val); return val; } EOF if compile_prog "" "" "__sync_fetch_and_add()" ; then sfaa="yes" fi echo "__sync_fetch_and_add $sfaa" ########################################## # libverbs probe libverbs="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { struct ibv_pd *pd = ibv_alloc_pd(NULL); return 0; } EOF if compile_prog "" "-libverbs" "libverbs" ; then libverbs="yes" LIBS="-libverbs $LIBS" fi echo "libverbs $libverbs" ########################################## # rdmacm probe rdmacm="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { rdma_destroy_qp(NULL); return 0; } EOF if compile_prog "" "-lrdmacm" "rdma"; then rdmacm="yes" LIBS="-lrdmacm $LIBS" fi echo "rdmacm $rdmacm" ########################################## # Linux fallocate probe linux_fallocate="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { int r = fallocate(0, FALLOC_FL_KEEP_SIZE, 0, 1024); return r; } EOF if compile_prog "" "" "linux_fallocate"; then linux_fallocate="yes" fi echo "Linux fallocate $linux_fallocate" ########################################## # POSIX fadvise probe posix_fadvise="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { int r = posix_fadvise(0, 0, 0, POSIX_FADV_NORMAL); return r; } EOF if compile_prog "" "" "posix_fadvise"; then posix_fadvise="yes" fi echo "POSIX fadvise $posix_fadvise" ########################################## # POSIX fallocate probe posix_fallocate="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { int r = posix_fallocate(0, 0, 1024); return r; } EOF if compile_prog "" "" "posix_fallocate"; then posix_fallocate="yes" fi echo "POSIX fallocate $posix_fallocate" ########################################## # sched_set/getaffinity 2 or 3 argument test linux_2arg_affinity="no" linux_3arg_affinity="no" cat > $TMPC << EOF #include int main(int argc, char **argv) { cpu_set_t mask; return sched_setaffinity(0, sizeof(mask), &mask); } EOF if compile_prog "" "" "sched_setaffinity(,,)"; then linux_3arg_affinity="yes" else cat > $TMPC << EOF #include int main(int argc, char **argv) { cpu_set_t mask; return sched_setaffinity(0, &mask); } EOF if compile_prog "" "" "sched_setaffinity(,)"; then linux_2arg_affinity="yes" fi fi echo "sched_setaffinity(3 arg) $linux_3arg_affinity" echo "sched_setaffinity(2 arg) $linux_2arg_affinity" ########################################## # clock_gettime probe clock_gettime="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { return clock_gettime(0, NULL); } EOF if compile_prog "" "" "clock_gettime"; then clock_gettime="yes" elif compile_prog "" "-lrt" "clock_gettime"; then clock_gettime="yes" LIBS="-lrt $LIBS" fi echo "clock_gettime $clock_gettime" ########################################## # CLOCK_MONOTONIC probe clock_monotonic="no" if test "$clock_gettime" = "yes" ; then cat > $TMPC << EOF #include #include int main(int argc, char **argv) { return clock_gettime(CLOCK_MONOTONIC, NULL); } EOF if compile_prog "" "$LIBS" "clock monotonic"; then clock_monotonic="yes" fi fi echo "CLOCK_MONOTONIC $clock_monotonic" ########################################## # CLOCK_MONOTONIC_PRECISE probe clock_monotonic_precise="no" if test "$clock_gettime" = "yes" ; then cat > $TMPC << EOF #include #include int main(int argc, char **argv) { return clock_gettime(CLOCK_MONOTONIC_PRECISE, NULL); } EOF if compile_prog "" "$LIBS" "clock monotonic precise"; then clock_monotonic_precise="yes" fi fi echo "CLOCK_MONOTONIC_PRECISE $clock_monotonic_precise" ########################################## # gettimeofday() probe gettimeofday="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { struct timeval tv; return gettimeofday(&tv, NULL); } EOF if compile_prog "" "" "gettimeofday"; then gettimeofday="yes" fi echo "gettimeofday $gettimeofday" ########################################## # fdatasync() probe fdatasync="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { return fdatasync(0); } EOF if compile_prog "" "" "fdatasync"; then fdatasync="yes" fi echo "fdatasync $fdatasync" ########################################## # sync_file_range() probe sync_file_range="no" cat > $TMPC << EOF #include #include #include #include int main(int argc, char **argv) { unsigned int flags = SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER; return sync_file_range(0, 0, 0, flags); } EOF if compile_prog "" "" "sync_file_range"; then sync_file_range="yes" fi echo "sync_file_range $sync_file_range" ########################################## # ext4 move extent probe ext4_me="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { struct move_extent me; return ioctl(0, EXT4_IOC_MOVE_EXT, &me); } EOF if compile_prog "" "" "ext4 move extent" ; then ext4_me="yes" elif test $targetos = "Linux" ; then # On Linux, just default to it on and let it error at runtime if we really # don't have it. None of my updated systems have it defined, but it does # work. Takes a while to bubble back. ext4_me="yes" fi echo "EXT4 move extent $ext4_me" ########################################## # splice probe linux_splice="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { return splice(0, NULL, 0, NULL, 0, SPLICE_F_NONBLOCK); } EOF if compile_prog "" "" "linux splice"; then linux_splice="yes" fi echo "Linux splice(2) $linux_splice" ########################################## # GUASI probe guasi="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { guasi_t ctx = guasi_create(0, 0, 0); return 0; } EOF if compile_prog "" "" "guasi"; then guasi="yes" fi echo "GUASI $guasi" ########################################## # fusion-aw probe fusion_aw="no" cat > $TMPC << EOF #include int main(int argc, char **argv) { nvm_version_t ver_info; nvm_handle_t handle; handle = nvm_get_handle(0, &ver_info); return nvm_atomic_write(handle, 0, 0, 0); } EOF if compile_prog "" "-L/usr/lib/fio -L/usr/lib/nvm -lnvm-primitives -lvsl -ldl" "fusion-aw"; then LIBS="-L/usr/lib/fio -L/usr/lib/nvm -lnvm-primitives -lvsl -ldl $LIBS" fusion_aw="yes" fi echo "Fusion-io atomic engine $fusion_aw" ########################################## # libnuma probe libnuma="no" cat > $TMPC << EOF #include int main(int argc, char **argv) { return numa_available(); } EOF if compile_prog "" "-lnuma" "libnuma"; then libnuma="yes" LIBS="-lnuma $LIBS" fi echo "libnuma $libnuma" ########################################## # libnuma 2.x version API if test "$libnuma" = "yes" ; then libnuma_v2="no" cat > $TMPC << EOF #include int main(int argc, char **argv) { struct bitmask *mask = numa_parse_nodestring(NULL); return 0; } EOF if compile_prog "" "" "libnuma api"; then libnuma_v2="yes" fi echo "libnuma v2 $libnuma_v2" fi ########################################## # strsep() probe strsep="no" cat > $TMPC << EOF #include int main(int argc, char **argv) { strsep(NULL, NULL); return 0; } EOF if compile_prog "" "" "strsep"; then strsep="yes" fi echo "strsep $strsep" ########################################## # strcasestr() probe strcasestr="no" cat > $TMPC << EOF #include int main(int argc, char **argv) { strcasestr(NULL, NULL); return 0; } EOF if compile_prog "" "" "strcasestr"; then strcasestr="yes" fi echo "strcasestr $strcasestr" ########################################## # getopt_long_only() probe getopt_long_only="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { int c = getopt_long_only(argc, argv, NULL, NULL, NULL); return c; } EOF if compile_prog "" "" "getopt_long_only"; then getopt_long_only="yes" fi echo "getopt_long_only() $getopt_long_only" ########################################## # inet_aton() probe inet_aton="no" cat > $TMPC << EOF #include #include #include int main(int argc, char **argv) { struct in_addr in; return inet_aton(NULL, &in); } EOF if compile_prog "" "" "inet_aton"; then inet_aton="yes" fi echo "inet_aton $inet_aton" ########################################## # socklen_t probe socklen_t="no" cat > $TMPC << EOF #include int main(int argc, char **argv) { socklen_t len = 0; return len; } EOF if compile_prog "" "" "socklen_t"; then socklen_t="yes" fi echo "socklen_t $socklen_t" ########################################## # Whether or not __thread is supported for TLS tls_thread="no" cat > $TMPC << EOF #include static int __thread ret; int main(int argc, char **argv) { return ret; } EOF if compile_prog "" "" "__thread"; then tls_thread="yes" fi echo "__thread $tls_thread" ########################################## # Check if we have required gtk/glib support for gfio if test "$gfio" = "yes" ; then cat > $TMPC << EOF #include #include #include int main(void) { gdk_threads_enter(); gdk_threads_leave(); printf("%d", GTK_CHECK_VERSION(2, 18, 0)); } EOF GTK_CFLAGS=$(pkg-config --cflags gtk+-2.0 gthread-2.0) if test "$?" != "0" ; then echo "configure: gtk and gthread not found" exit 1 fi GTK_LIBS=$(pkg-config --libs gtk+-2.0 gthread-2.0) if test "$?" != "0" ; then echo "configure: gtk and gthread not found" exit 1 fi if compile_prog "$GTK_CFLAGS" "$GTK_LIBS" "gfio" ; then r=$($TMPE) if test "$r" != "0" ; then gfio="yes" LIBS="$LIBS $GTK_LIBS" CFLAGS="$CFLAGS $GTK_CFLAGS" else echo "GTK found, but need version 2.18 or higher" gfio="no" fi else echo "Please install gtk and gdk libraries" gfio="no" fi fi echo "gtk 2.18 or higher $gfio" # Check whether we have getrusage(RUSAGE_THREAD) rusage_thread="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { struct rusage ru; getrusage(RUSAGE_THREAD, &ru); return 0; } EOF if compile_prog "" "" "RUSAGE_THREAD"; then rusage_thread="yes" fi echo "RUSAGE_THREAD $rusage_thread" ########################################## # Check whether we have SCHED_IDLE sched_idle="no" cat > $TMPC << EOF #include int main(int argc, char **argv) { struct sched_param p; return sched_setscheduler(0, SCHED_IDLE, &p); } EOF if compile_prog "" "" "SCHED_IDLE"; then sched_idle="yes" fi echo "SCHED_IDLE $sched_idle" ########################################## # Check whether we have TCP_NODELAY tcp_nodelay="no" cat > $TMPC << EOF #include #include #include #include int main(int argc, char **argv) { return getsockopt(0, 0, TCP_NODELAY, NULL, NULL); } EOF if compile_prog "" "" "TCP_NODELAY"; then tcp_nodelay="yes" fi echo "TCP_NODELAY $tcp_nodelay" ########################################## # Check whether we have RLIMIT_MEMLOCK rlimit_memlock="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { struct rlimit rl; return getrlimit(RLIMIT_MEMLOCK, &rl); } EOF if compile_prog "" "" "RLIMIT_MEMLOCK"; then rlimit_memlock="yes" fi echo "RLIMIT_MEMLOCK $rlimit_memlock" ########################################## # Check whether we have pwritev/preadv pwritev="no" cat > $TMPC << EOF #include #include int main(int argc, char **argv) { return pwritev(0, NULL, 1, 0) + preadv(0, NULL, 1, 0); } EOF if compile_prog "" "" "pwritev"; then pwritev="yes" fi echo "pwritev/preadv $pwritev" ############################################################################# if test "$wordsize" = "64" ; then output_sym "CONFIG_64BIT" elif test "$wordsize" = "32" ; then output_sym "CONFIG_32BIT" else fatal "Unknown wordsize!" fi if test "$bigendian" = "yes" ; then output_sym "CONFIG_BIG_ENDIAN" else output_sym "CONFIG_LITTLE_ENDIAN" fi if test "$zlib" = "yes" ; then output_sym "CONFIG_ZLIB" fi if test "$libaio" = "yes" ; then output_sym "CONFIG_LIBAIO" fi if test "$posix_aio" = "yes" ; then output_sym "CONFIG_POSIXAIO" fi if test "$posix_aio_fsync" = "yes" ; then output_sym "CONFIG_POSIXAIO_FSYNC" fi if test "$linux_fallocate" = "yes" ; then output_sym "CONFIG_LINUX_FALLOCATE" fi if test "$posix_fallocate" = "yes" ; then output_sym "CONFIG_POSIX_FALLOCATE" fi if test "$fdatasync" = "yes" ; then output_sym "CONFIG_FDATASYNC" fi if test "$sync_file_range" = "yes" ; then output_sym "CONFIG_SYNC_FILE_RANGE" fi if test "$sfaa" = "yes" ; then output_sym "CONFIG_SFAA" fi if test "$libverbs" = "yes" -a "$rdmacm" = "yes" ; then output_sym "CONFIG_RDMA" fi if test "$clock_gettime" = "yes" ; then output_sym "CONFIG_CLOCK_GETTIME" fi if test "$clock_monotonic" = "yes" ; then output_sym "CONFIG_CLOCK_MONOTONIC" fi if test "$clock_monotonic_precise" = "yes" ; then output_sym "CONFIG_CLOCK_MONOTONIC_PRECISE" fi if test "$gettimeofday" = "yes" ; then output_sym "CONFIG_GETTIMEOFDAY" fi if test "$posix_fadvise" = "yes" ; then output_sym "CONFIG_POSIX_FADVISE" fi if test "$linux_3arg_affinity" = "yes" ; then output_sym "CONFIG_3ARG_AFFINITY" elif test "$linux_2arg_affinity" = "yes" ; then output_sym "CONFIG_2ARG_AFFINITY" fi if test "$strsep" = "yes" ; then output_sym "CONFIG_STRSEP" fi if test "$strcasestr" = "yes" ; then output_sym "CONFIG_STRCASESTR" fi if test "$getopt_long_only" = "yes" ; then output_sym "CONFIG_GETOPT_LONG_ONLY" fi if test "$inet_aton" = "yes" ; then output_sym "CONFIG_INET_ATON" fi if test "$socklen_t" = "yes" ; then output_sym "CONFIG_SOCKLEN_T" fi if test "$ext4_me" = "yes" ; then output_sym "CONFIG_LINUX_EXT4_MOVE_EXTENT" fi if test "$linux_splice" = "yes" ; then output_sym "CONFIG_LINUX_SPLICE" fi if test "$guasi" = "yes" ; then output_sym "CONFIG_GUASI" fi if test "$fusion_aw" = "yes" ; then output_sym "CONFIG_FUSION_AW" fi if test "$libnuma_v2" = "yes" ; then output_sym "CONFIG_LIBNUMA" fi if test "$solaris_aio" = "yes" ; then output_sym "CONFIG_SOLARISAIO" fi if test "$tls_thread" = "yes" ; then output_sym "CONFIG_TLS_THREAD" fi if test "$rusage_thread" = "yes" ; then output_sym "CONFIG_RUSAGE_THREAD" fi if test "$gfio" = "yes" ; then echo "CONFIG_GFIO=y" >> $config_host_mak fi if test "$sched_idle" = "yes" ; then output_sym "CONFIG_SCHED_IDLE" fi if test "$tcp_nodelay" = "yes" ; then output_sym "CONFIG_TCP_NODELAY" fi if test "$rlimit_memlock" = "yes" ; then output_sym "CONFIG_RLIMIT_MEMLOCK" fi if test "$pwritev" = "yes" ; then output_sym "CONFIG_PWRITEV" fi echo "LIBS+=$LIBS" >> $config_host_mak echo "CFLAGS+=$CFLAGS" >> $config_host_mak echo "CC=$cc" >> $config_host_mak echo "BUILD_CFLAGS=$BUILD_CFLAGS $CFLAGS" >> $config_host_mak fio-2.1.3/crc/000077500000000000000000000000001222032232000130255ustar00rootroot00000000000000fio-2.1.3/crc/crc16.c000066400000000000000000000050271222032232000141130ustar00rootroot00000000000000/* * crc16.c * * This source code is licensed under the GNU General Public License, * Version 2. See the file COPYING for more details. */ #include "crc16.h" /** CRC table for the CRC-16. The poly is 0x8005 (x^16 + x^15 + x^2 + 1) */ unsigned short const crc16_table[256] = { 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 }; unsigned short fio_crc16(const void *buffer, unsigned int len) { const unsigned char *cp = (const unsigned char *) buffer; unsigned short crc = 0; while (len--) crc = crc16_byte(crc, *cp++); return crc; } fio-2.1.3/crc/crc16.h000066400000000000000000000012221222032232000141110ustar00rootroot00000000000000/* * crc16.h - CRC-16 routine * * Implements the standard CRC-16: * Width 16 * Poly 0x8005 (x^16 + x^15 + x^2 + 1) * Init 0 * * Copyright (c) 2005 Ben Gardner * * This source code is licensed under the GNU General Public License, * Version 2. See the file COPYING for more details. */ #ifndef __CRC16_H #define __CRC16_H extern unsigned short const crc16_table[256]; extern unsigned short fio_crc16(const void *buffer, unsigned int len); static inline unsigned short crc16_byte(unsigned short crc, const unsigned char data) { return (crc >> 8) ^ crc16_table[(crc ^ data) & 0xff]; } #endif /* __CRC16_H */ fio-2.1.3/crc/crc32.c000066400000000000000000000103101222032232000141000ustar00rootroot00000000000000/* crc32 -- calculate and POSIX.2 checksum Copyright (C) 92, 1995-1999 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "crc32.h" static const uint32_t crctab[256] = { 0x0, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072, 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3, 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4 }; uint32_t fio_crc32(const void *buffer, unsigned long length) { const unsigned char *cp = (const unsigned char *) buffer; uint32_t crc = 0; while (length--) crc = (crc << 8) ^ crctab[((crc >> 24) ^ *(cp++)) & 0xFF]; return crc; } fio-2.1.3/crc/crc32.h000066400000000000000000000016161222032232000141160ustar00rootroot00000000000000/* crc32 -- calculate and POSIX.2 checksum Copyright (C) 92, 1995-1999 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef CRC32_H #define CRC32_H extern uint32_t fio_crc32(const void * const, unsigned long); #endif fio-2.1.3/crc/crc32c-intel.c000066400000000000000000000037111222032232000153630ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "crc32c.h" /* * Based on a posting to lkml by Austin Zhang * * Using hardware provided CRC32 instruction to accelerate the CRC32 disposal. * CRC32C polynomial:0x1EDC6F41(BE)/0x82F63B78(LE) * CRC32 is a new instruction in Intel SSE4.2, the reference can be found at: * http://www.intel.com/products/processor/manuals/ * Intel(R) 64 and IA-32 Architectures Software Developer's Manual * Volume 2A: Instruction Set Reference, A-M */ int crc32c_intel_available = 0; #ifdef ARCH_HAVE_SSE4_2 #if BITS_PER_LONG == 64 #define REX_PRE "0x48, " #define SCALE_F 8 #else #define REX_PRE #define SCALE_F 4 #endif static int crc32c_probed; static uint32_t crc32c_intel_le_hw_byte(uint32_t crc, unsigned char const *data, unsigned long length) { while (length--) { __asm__ __volatile__( ".byte 0xf2, 0xf, 0x38, 0xf0, 0xf1" :"=S"(crc) :"0"(crc), "c"(*data) ); data++; } return crc; } /* * Steps through buffer one byte at at time, calculates reflected * crc using table. */ uint32_t crc32c_intel(unsigned char const *data, unsigned long length) { unsigned int iquotient = length / SCALE_F; unsigned int iremainder = length % SCALE_F; #if BITS_PER_LONG == 64 uint64_t *ptmp = (uint64_t *) data; #else uint32_t *ptmp = (uint32_t *) data; #endif uint32_t crc = ~0; while (iquotient--) { __asm__ __volatile__( ".byte 0xf2, " REX_PRE "0xf, 0x38, 0xf1, 0xf1;" :"=S"(crc) :"0"(crc), "c"(*ptmp) ); ptmp++; } if (iremainder) crc = crc32c_intel_le_hw_byte(crc, (unsigned char *)ptmp, iremainder); return crc; } void crc32c_intel_probe(void) { if (!crc32c_probed) { unsigned int eax, ebx, ecx = 0, edx; eax = 1; do_cpuid(&eax, &ebx, &ecx, &edx); crc32c_intel_available = (ecx & (1 << 20)) != 0; crc32c_probed = 1; } } #endif /* ARCH_HAVE_SSE */ fio-2.1.3/crc/crc32c.c000066400000000000000000000117511222032232000142550ustar00rootroot00000000000000/* * CRC32C *@Article{castagnoli-crc, * author = { Guy Castagnoli and Stefan Braeuer and Martin Herrman}, * title = {{Optimization of Cyclic Redundancy-Check Codes with 24 * and 32 Parity Bits}}, * journal = IEEE Transactions on Communication, * year = {1993}, * volume = {41}, * number = {6}, * pages = {}, * month = {June}, *} * Used by the iSCSI driver, possibly others, and derived from the * the iscsi-crc.c module of the linux-iscsi driver at * http://linux-iscsi.sourceforge.net. * * Following the example of lib/crc32, this function is intended to be * flexible and useful for all users. Modules that currently have their * own crc32c, but hopefully may be able to use this one are: * net/sctp (please add all your doco to here if you change to * use this one!) * * * Copyright (c) 2004 Cisco Systems, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * */ #include /* * This is the CRC-32C table * Generated with: * width = 32 bits * poly = 0x1EDC6F41 * reflect input bytes = true * reflect output bytes = true */ static const uint32_t crc32c_table[256] = { 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L, 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL, 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL, 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L, 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL, 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L, 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L, 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L, 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L, 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L, 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L, 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL, 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L, 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL, 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL, 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL, 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L, 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL, 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L, 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL, 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L, 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L, 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L, 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL, 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L }; /* * Steps through buffer one byte at at time, calculates reflected * crc using table. */ uint32_t crc32c_sw(unsigned char const *data, unsigned long length) { uint32_t crc = ~0; while (length--) crc = crc32c_table[(crc ^ *data++) & 0xFFL] ^ (crc >> 8); return crc; } fio-2.1.3/crc/crc32c.h000066400000000000000000000025411222032232000142570ustar00rootroot00000000000000/* crc32c -- calculate and POSIX.2 checksum Copyright (C) 92, 1995-1999 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef CRC32C_H #define CRC32C_H #include "../arch/arch.h" extern uint32_t crc32c_sw(unsigned char const *, unsigned long); extern int crc32c_intel_available; #ifdef ARCH_HAVE_SSE4_2 extern uint32_t crc32c_intel(unsigned char const *, unsigned long); extern void crc32c_intel_probe(void); #else #define crc32c_intel crc32c_sw static inline void crc32c_intel_probe(void) { } #endif static inline uint32_t fio_crc32c(unsigned char const *buf, unsigned long len) { if (crc32c_intel_available) return crc32c_intel(buf, len); return crc32c_sw(buf, len); } #endif fio-2.1.3/crc/crc64.c000066400000000000000000000144071222032232000141200ustar00rootroot00000000000000#include "crc64.h" /* * poly 0x95AC9329AC4BC9B5ULL and init 0xFFFFFFFFFFFFFFFFULL */ static const unsigned long long crctab64[256] = { 0x0000000000000000ULL, 0x7ad870c830358979ULL, 0xf5b0e190606b12f2ULL, 0x8f689158505e9b8bULL, 0xc038e5739841b68fULL, 0xbae095bba8743ff6ULL, 0x358804e3f82aa47dULL, 0x4f50742bc81f2d04ULL, 0xab28ecb46814fe75ULL, 0xd1f09c7c5821770cULL, 0x5e980d24087fec87ULL, 0x24407dec384a65feULL, 0x6b1009c7f05548faULL, 0x11c8790fc060c183ULL, 0x9ea0e857903e5a08ULL, 0xe478989fa00bd371ULL, 0x7d08ff3b88be6f81ULL, 0x07d08ff3b88be6f8ULL, 0x88b81eabe8d57d73ULL, 0xf2606e63d8e0f40aULL, 0xbd301a4810ffd90eULL, 0xc7e86a8020ca5077ULL, 0x4880fbd87094cbfcULL, 0x32588b1040a14285ULL, 0xd620138fe0aa91f4ULL, 0xacf86347d09f188dULL, 0x2390f21f80c18306ULL, 0x594882d7b0f40a7fULL, 0x1618f6fc78eb277bULL, 0x6cc0863448deae02ULL, 0xe3a8176c18803589ULL, 0x997067a428b5bcf0ULL, 0xfa11fe77117cdf02ULL, 0x80c98ebf2149567bULL, 0x0fa11fe77117cdf0ULL, 0x75796f2f41224489ULL, 0x3a291b04893d698dULL, 0x40f16bccb908e0f4ULL, 0xcf99fa94e9567b7fULL, 0xb5418a5cd963f206ULL, 0x513912c379682177ULL, 0x2be1620b495da80eULL, 0xa489f35319033385ULL, 0xde51839b2936bafcULL, 0x9101f7b0e12997f8ULL, 0xebd98778d11c1e81ULL, 0x64b116208142850aULL, 0x1e6966e8b1770c73ULL, 0x8719014c99c2b083ULL, 0xfdc17184a9f739faULL, 0x72a9e0dcf9a9a271ULL, 0x08719014c99c2b08ULL, 0x4721e43f0183060cULL, 0x3df994f731b68f75ULL, 0xb29105af61e814feULL, 0xc849756751dd9d87ULL, 0x2c31edf8f1d64ef6ULL, 0x56e99d30c1e3c78fULL, 0xd9810c6891bd5c04ULL, 0xa3597ca0a188d57dULL, 0xec09088b6997f879ULL, 0x96d1784359a27100ULL, 0x19b9e91b09fcea8bULL, 0x636199d339c963f2ULL, 0xdf7adabd7a6e2d6fULL, 0xa5a2aa754a5ba416ULL, 0x2aca3b2d1a053f9dULL, 0x50124be52a30b6e4ULL, 0x1f423fcee22f9be0ULL, 0x659a4f06d21a1299ULL, 0xeaf2de5e82448912ULL, 0x902aae96b271006bULL, 0x74523609127ad31aULL, 0x0e8a46c1224f5a63ULL, 0x81e2d7997211c1e8ULL, 0xfb3aa75142244891ULL, 0xb46ad37a8a3b6595ULL, 0xceb2a3b2ba0eececULL, 0x41da32eaea507767ULL, 0x3b024222da65fe1eULL, 0xa2722586f2d042eeULL, 0xd8aa554ec2e5cb97ULL, 0x57c2c41692bb501cULL, 0x2d1ab4dea28ed965ULL, 0x624ac0f56a91f461ULL, 0x1892b03d5aa47d18ULL, 0x97fa21650afae693ULL, 0xed2251ad3acf6feaULL, 0x095ac9329ac4bc9bULL, 0x7382b9faaaf135e2ULL, 0xfcea28a2faafae69ULL, 0x8632586aca9a2710ULL, 0xc9622c4102850a14ULL, 0xb3ba5c8932b0836dULL, 0x3cd2cdd162ee18e6ULL, 0x460abd1952db919fULL, 0x256b24ca6b12f26dULL, 0x5fb354025b277b14ULL, 0xd0dbc55a0b79e09fULL, 0xaa03b5923b4c69e6ULL, 0xe553c1b9f35344e2ULL, 0x9f8bb171c366cd9bULL, 0x10e3202993385610ULL, 0x6a3b50e1a30ddf69ULL, 0x8e43c87e03060c18ULL, 0xf49bb8b633338561ULL, 0x7bf329ee636d1eeaULL, 0x012b592653589793ULL, 0x4e7b2d0d9b47ba97ULL, 0x34a35dc5ab7233eeULL, 0xbbcbcc9dfb2ca865ULL, 0xc113bc55cb19211cULL, 0x5863dbf1e3ac9decULL, 0x22bbab39d3991495ULL, 0xadd33a6183c78f1eULL, 0xd70b4aa9b3f20667ULL, 0x985b3e827bed2b63ULL, 0xe2834e4a4bd8a21aULL, 0x6debdf121b863991ULL, 0x1733afda2bb3b0e8ULL, 0xf34b37458bb86399ULL, 0x8993478dbb8deae0ULL, 0x06fbd6d5ebd3716bULL, 0x7c23a61ddbe6f812ULL, 0x3373d23613f9d516ULL, 0x49aba2fe23cc5c6fULL, 0xc6c333a67392c7e4ULL, 0xbc1b436e43a74e9dULL, 0x95ac9329ac4bc9b5ULL, 0xef74e3e19c7e40ccULL, 0x601c72b9cc20db47ULL, 0x1ac40271fc15523eULL, 0x5594765a340a7f3aULL, 0x2f4c0692043ff643ULL, 0xa02497ca54616dc8ULL, 0xdafce7026454e4b1ULL, 0x3e847f9dc45f37c0ULL, 0x445c0f55f46abeb9ULL, 0xcb349e0da4342532ULL, 0xb1eceec59401ac4bULL, 0xfebc9aee5c1e814fULL, 0x8464ea266c2b0836ULL, 0x0b0c7b7e3c7593bdULL, 0x71d40bb60c401ac4ULL, 0xe8a46c1224f5a634ULL, 0x927c1cda14c02f4dULL, 0x1d148d82449eb4c6ULL, 0x67ccfd4a74ab3dbfULL, 0x289c8961bcb410bbULL, 0x5244f9a98c8199c2ULL, 0xdd2c68f1dcdf0249ULL, 0xa7f41839ecea8b30ULL, 0x438c80a64ce15841ULL, 0x3954f06e7cd4d138ULL, 0xb63c61362c8a4ab3ULL, 0xcce411fe1cbfc3caULL, 0x83b465d5d4a0eeceULL, 0xf96c151de49567b7ULL, 0x76048445b4cbfc3cULL, 0x0cdcf48d84fe7545ULL, 0x6fbd6d5ebd3716b7ULL, 0x15651d968d029fceULL, 0x9a0d8ccedd5c0445ULL, 0xe0d5fc06ed698d3cULL, 0xaf85882d2576a038ULL, 0xd55df8e515432941ULL, 0x5a3569bd451db2caULL, 0x20ed197575283bb3ULL, 0xc49581ead523e8c2ULL, 0xbe4df122e51661bbULL, 0x3125607ab548fa30ULL, 0x4bfd10b2857d7349ULL, 0x04ad64994d625e4dULL, 0x7e7514517d57d734ULL, 0xf11d85092d094cbfULL, 0x8bc5f5c11d3cc5c6ULL, 0x12b5926535897936ULL, 0x686de2ad05bcf04fULL, 0xe70573f555e26bc4ULL, 0x9ddd033d65d7e2bdULL, 0xd28d7716adc8cfb9ULL, 0xa85507de9dfd46c0ULL, 0x273d9686cda3dd4bULL, 0x5de5e64efd965432ULL, 0xb99d7ed15d9d8743ULL, 0xc3450e196da80e3aULL, 0x4c2d9f413df695b1ULL, 0x36f5ef890dc31cc8ULL, 0x79a59ba2c5dc31ccULL, 0x037deb6af5e9b8b5ULL, 0x8c157a32a5b7233eULL, 0xf6cd0afa9582aa47ULL, 0x4ad64994d625e4daULL, 0x300e395ce6106da3ULL, 0xbf66a804b64ef628ULL, 0xc5bed8cc867b7f51ULL, 0x8aeeace74e645255ULL, 0xf036dc2f7e51db2cULL, 0x7f5e4d772e0f40a7ULL, 0x05863dbf1e3ac9deULL, 0xe1fea520be311aafULL, 0x9b26d5e88e0493d6ULL, 0x144e44b0de5a085dULL, 0x6e963478ee6f8124ULL, 0x21c640532670ac20ULL, 0x5b1e309b16452559ULL, 0xd476a1c3461bbed2ULL, 0xaeaed10b762e37abULL, 0x37deb6af5e9b8b5bULL, 0x4d06c6676eae0222ULL, 0xc26e573f3ef099a9ULL, 0xb8b627f70ec510d0ULL, 0xf7e653dcc6da3dd4ULL, 0x8d3e2314f6efb4adULL, 0x0256b24ca6b12f26ULL, 0x788ec2849684a65fULL, 0x9cf65a1b368f752eULL, 0xe62e2ad306bafc57ULL, 0x6946bb8b56e467dcULL, 0x139ecb4366d1eea5ULL, 0x5ccebf68aecec3a1ULL, 0x2616cfa09efb4ad8ULL, 0xa97e5ef8cea5d153ULL, 0xd3a62e30fe90582aULL, 0xb0c7b7e3c7593bd8ULL, 0xca1fc72bf76cb2a1ULL, 0x45775673a732292aULL, 0x3faf26bb9707a053ULL, 0x70ff52905f188d57ULL, 0x0a2722586f2d042eULL, 0x854fb3003f739fa5ULL, 0xff97c3c80f4616dcULL, 0x1bef5b57af4dc5adULL, 0x61372b9f9f784cd4ULL, 0xee5fbac7cf26d75fULL, 0x9487ca0fff135e26ULL, 0xdbd7be24370c7322ULL, 0xa10fceec0739fa5bULL, 0x2e675fb4576761d0ULL, 0x54bf2f7c6752e8a9ULL, 0xcdcf48d84fe75459ULL, 0xb71738107fd2dd20ULL, 0x387fa9482f8c46abULL, 0x42a7d9801fb9cfd2ULL, 0x0df7adabd7a6e2d6ULL, 0x772fdd63e7936bafULL, 0xf8474c3bb7cdf024ULL, 0x829f3cf387f8795dULL, 0x66e7a46c27f3aa2cULL, 0x1c3fd4a417c62355ULL, 0x935745fc4798b8deULL, 0xe98f353477ad31a7ULL, 0xa6df411fbfb21ca3ULL, 0xdc0731d78f8795daULL, 0x536fa08fdfd90e51ULL, 0x29b7d047efec8728ULL }; unsigned long long fio_crc64(const unsigned char *buffer, unsigned long length) { unsigned long long crc = 0; while (length--) crc = crctab64[(crc ^ *(buffer++)) & 0xff] ^ (crc >> 8); return crc; } fio-2.1.3/crc/crc64.h000066400000000000000000000001551222032232000141200ustar00rootroot00000000000000#ifndef CRC64_H #define CRC64_H unsigned long long fio_crc64(const unsigned char *, unsigned long); #endif fio-2.1.3/crc/crc7.c000066400000000000000000000037171222032232000140370ustar00rootroot00000000000000/* * crc7.c * * This source code is licensed under the GNU General Public License, * Version 2. See the file COPYING for more details. */ #include "crc7.h" /* Table for CRC-7 (polynomial x^7 + x^3 + 1) */ const unsigned char crc7_syndrome_table[256] = { 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, 0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e, 0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45, 0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c, 0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13, 0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a, 0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21, 0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, 0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36, 0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d, 0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52, 0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b, 0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60, 0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79 }; unsigned char fio_crc7(const unsigned char *buffer, unsigned int len) { unsigned char crc = 0; while (len--) crc = crc7_byte(crc, *buffer++); return crc; } fio-2.1.3/crc/crc7.h000066400000000000000000000004551222032232000140400ustar00rootroot00000000000000#ifndef CRC7_H #define CRC7_H extern const unsigned char crc7_syndrome_table[256]; static inline unsigned char crc7_byte(unsigned char crc, unsigned char data) { return crc7_syndrome_table[(crc << 1) ^ data]; } extern unsigned char fio_crc7(const unsigned char *buffer, unsigned int len); #endif fio-2.1.3/crc/md5.c000066400000000000000000000104601222032232000136570ustar00rootroot00000000000000/* * Shamelessly lifted from the 2.6 kernel (crypto/md5.c) */ #include #include #include "md5.h" static void md5_transform(uint32_t *hash, uint32_t const *in) { uint32_t a, b, c, d; a = hash[0]; b = hash[1]; c = hash[2]; d = hash[3]; MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); hash[0] += a; hash[1] += b; hash[2] += c; hash[3] += d; } void fio_md5_init(struct fio_md5_ctx *mctx) { mctx->hash[0] = 0x67452301; mctx->hash[1] = 0xefcdab89; mctx->hash[2] = 0x98badcfe; mctx->hash[3] = 0x10325476; } void fio_md5_update(struct fio_md5_ctx *mctx, const uint8_t *data, unsigned int len) { const uint32_t avail = sizeof(mctx->block) - (mctx->byte_count & 0x3f); mctx->byte_count += len; if (avail > len) { memcpy((char *)mctx->block + (sizeof(mctx->block) - avail), data, len); return; } memcpy((char *)mctx->block + (sizeof(mctx->block) - avail), data, avail); md5_transform(mctx->hash, mctx->block); data += avail; len -= avail; while (len >= sizeof(mctx->block)) { memcpy(mctx->block, data, sizeof(mctx->block)); md5_transform(mctx->hash, mctx->block); data += sizeof(mctx->block); len -= sizeof(mctx->block); } memcpy(mctx->block, data, len); } fio-2.1.3/crc/md5.h000066400000000000000000000011701222032232000136620ustar00rootroot00000000000000#ifndef MD5_H #define MD5_H #include #define MD5_DIGEST_SIZE 16 #define MD5_HMAC_BLOCK_SIZE 64 #define MD5_BLOCK_WORDS 16 #define MD5_HASH_WORDS 4 #define F1(x, y, z) (z ^ (x & (y ^ z))) #define F2(x, y, z) F1(z, x, y) #define F3(x, y, z) (x ^ y ^ z) #define F4(x, y, z) (y ^ (x | ~z)) #define MD5STEP(f, w, x, y, z, in, s) \ (w += f(x, y, z) + in, w = (w<>(32-s)) + x) struct fio_md5_ctx { uint32_t *hash; uint32_t block[MD5_BLOCK_WORDS]; uint64_t byte_count; }; extern void fio_md5_update(struct fio_md5_ctx *, const uint8_t *, unsigned int); extern void fio_md5_init(struct fio_md5_ctx *); #endif fio-2.1.3/crc/sha1.c000066400000000000000000000134311222032232000140270ustar00rootroot00000000000000/* * Based on the Mozilla SHA1 (see mozilla-sha1/sha1.c), * optimized to do word accesses rather than byte accesses, * and to avoid unnecessary copies into the context array. */ #include #include #include "sha1.h" /* Hash one 64-byte block of data */ static void blk_SHA1Block(struct fio_sha1_ctx *ctx, const unsigned int *data); void fio_sha1_init(struct fio_sha1_ctx *ctx) { ctx->size = 0; /* Initialize H with the magic constants (see FIPS180 for constants) */ ctx->H[0] = 0x67452301; ctx->H[1] = 0xefcdab89; ctx->H[2] = 0x98badcfe; ctx->H[3] = 0x10325476; ctx->H[4] = 0xc3d2e1f0; } void fio_sha1_update(struct fio_sha1_ctx *ctx, const void *data, unsigned long len) { int lenW = ctx->size & 63; ctx->size += len; /* Read the data into W and process blocks as they get full */ if (lenW) { int left = 64 - lenW; if (len < left) left = len; memcpy(lenW + (char *)ctx->W, data, left); lenW = (lenW + left) & 63; len -= left; data += left; if (lenW) return; blk_SHA1Block(ctx, ctx->W); } while (len >= 64) { blk_SHA1Block(ctx, data); data += 64; len -= 64; } if (len) memcpy(ctx->W, data, len); } void fio_sha1_final(unsigned char hashout[20], struct fio_sha1_ctx *ctx) { static const unsigned char pad[64] = { 0x80 }; unsigned int padlen[2]; int i; /* Pad with a binary 1 (ie 0x80), then zeroes, then length */ padlen[0] = htonl(ctx->size >> 29); padlen[1] = htonl(ctx->size << 3); i = ctx->size & 63; fio_sha1_update(ctx, pad, 1+ (63 & (55 - i))); fio_sha1_update(ctx, padlen, 8); /* Output hash */ for (i = 0; i < 5; i++) ((unsigned int *)hashout)[i] = htonl(ctx->H[i]); } #if defined(__i386__) || defined(__x86_64__) #define SHA_ASM(op, x, n) ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; }) #define SHA_ROL(x,n) SHA_ASM("rol", x, n) #define SHA_ROR(x,n) SHA_ASM("ror", x, n) #else #define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r))) #define SHA_ROL(X,n) SHA_ROT(X,n,32-(n)) #define SHA_ROR(X,n) SHA_ROT(X,32-(n),n) #endif /* This "rolls" over the 512-bit array */ #define W(x) (array[(x)&15]) #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) /* * Where do we get the source from? The first 16 iterations get it from * the input data, the next mix it from the 512-bit array. */ #define SHA_SRC(t) htonl(data[t]) #define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1) #define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \ unsigned int TEMP = input(t); setW(t, TEMP); \ E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \ B = SHA_ROR(B, 2); } while (0) #define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) #define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) #define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E ) #define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E ) #define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) static void blk_SHA1Block(struct fio_sha1_ctx *ctx, const unsigned int *data) { unsigned int A,B,C,D,E; unsigned int array[16]; A = ctx->H[0]; B = ctx->H[1]; C = ctx->H[2]; D = ctx->H[3]; E = ctx->H[4]; /* Round 1 - iterations 0-16 take their input from 'data' */ T_0_15( 0, A, B, C, D, E); T_0_15( 1, E, A, B, C, D); T_0_15( 2, D, E, A, B, C); T_0_15( 3, C, D, E, A, B); T_0_15( 4, B, C, D, E, A); T_0_15( 5, A, B, C, D, E); T_0_15( 6, E, A, B, C, D); T_0_15( 7, D, E, A, B, C); T_0_15( 8, C, D, E, A, B); T_0_15( 9, B, C, D, E, A); T_0_15(10, A, B, C, D, E); T_0_15(11, E, A, B, C, D); T_0_15(12, D, E, A, B, C); T_0_15(13, C, D, E, A, B); T_0_15(14, B, C, D, E, A); T_0_15(15, A, B, C, D, E); /* Round 1 - tail. Input from 512-bit mixing array */ T_16_19(16, E, A, B, C, D); T_16_19(17, D, E, A, B, C); T_16_19(18, C, D, E, A, B); T_16_19(19, B, C, D, E, A); /* Round 2 */ T_20_39(20, A, B, C, D, E); T_20_39(21, E, A, B, C, D); T_20_39(22, D, E, A, B, C); T_20_39(23, C, D, E, A, B); T_20_39(24, B, C, D, E, A); T_20_39(25, A, B, C, D, E); T_20_39(26, E, A, B, C, D); T_20_39(27, D, E, A, B, C); T_20_39(28, C, D, E, A, B); T_20_39(29, B, C, D, E, A); T_20_39(30, A, B, C, D, E); T_20_39(31, E, A, B, C, D); T_20_39(32, D, E, A, B, C); T_20_39(33, C, D, E, A, B); T_20_39(34, B, C, D, E, A); T_20_39(35, A, B, C, D, E); T_20_39(36, E, A, B, C, D); T_20_39(37, D, E, A, B, C); T_20_39(38, C, D, E, A, B); T_20_39(39, B, C, D, E, A); /* Round 3 */ T_40_59(40, A, B, C, D, E); T_40_59(41, E, A, B, C, D); T_40_59(42, D, E, A, B, C); T_40_59(43, C, D, E, A, B); T_40_59(44, B, C, D, E, A); T_40_59(45, A, B, C, D, E); T_40_59(46, E, A, B, C, D); T_40_59(47, D, E, A, B, C); T_40_59(48, C, D, E, A, B); T_40_59(49, B, C, D, E, A); T_40_59(50, A, B, C, D, E); T_40_59(51, E, A, B, C, D); T_40_59(52, D, E, A, B, C); T_40_59(53, C, D, E, A, B); T_40_59(54, B, C, D, E, A); T_40_59(55, A, B, C, D, E); T_40_59(56, E, A, B, C, D); T_40_59(57, D, E, A, B, C); T_40_59(58, C, D, E, A, B); T_40_59(59, B, C, D, E, A); /* Round 4 */ T_60_79(60, A, B, C, D, E); T_60_79(61, E, A, B, C, D); T_60_79(62, D, E, A, B, C); T_60_79(63, C, D, E, A, B); T_60_79(64, B, C, D, E, A); T_60_79(65, A, B, C, D, E); T_60_79(66, E, A, B, C, D); T_60_79(67, D, E, A, B, C); T_60_79(68, C, D, E, A, B); T_60_79(69, B, C, D, E, A); T_60_79(70, A, B, C, D, E); T_60_79(71, E, A, B, C, D); T_60_79(72, D, E, A, B, C); T_60_79(73, C, D, E, A, B); T_60_79(74, B, C, D, E, A); T_60_79(75, A, B, C, D, E); T_60_79(76, E, A, B, C, D); T_60_79(77, D, E, A, B, C); T_60_79(78, C, D, E, A, B); T_60_79(79, B, C, D, E, A); ctx->H[0] += A; ctx->H[1] += B; ctx->H[2] += C; ctx->H[3] += D; ctx->H[4] += E; } fio-2.1.3/crc/sha1.h000066400000000000000000000007771222032232000140450ustar00rootroot00000000000000#ifndef FIO_SHA1 #define FIO_SHA1 /* * Based on the Mozilla SHA1 (see mozilla-sha1/sha1.h), * optimized to do word accesses rather than byte accesses, * and to avoid unnecessary copies into the context array. */ struct fio_sha1_ctx { uint32_t *H; unsigned int W[16]; unsigned long long size; }; void fio_sha1_init(struct fio_sha1_ctx *); void fio_sha1_update(struct fio_sha1_ctx *, const void *dataIn, unsigned long len); void fio_sha1_final(unsigned char hashout[20], struct fio_sha1_ctx *); #endif fio-2.1.3/crc/sha256.c000066400000000000000000000230621222032232000142040ustar00rootroot00000000000000/* * Cryptographic API. * * SHA-256, as specified in * http://csrc.nist.gov/cryptval/shs/sha256-384-512.pdf * * SHA-256 code by Jean-Luc Cooke . * * Copyright (c) Jean-Luc Cooke * Copyright (c) Andrew McDonald * Copyright (c) 2002 James Morris * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * */ #include #include #include "../lib/bswap.h" #include "sha256.h" #define SHA256_DIGEST_SIZE 32 #define SHA256_HMAC_BLOCK_SIZE 64 static inline uint32_t Ch(uint32_t x, uint32_t y, uint32_t z) { return z ^ (x & (y ^ z)); } static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) { return (x & y) | (z & (x | y)); } #define e0(x) (ror32(x, 2) ^ ror32(x,13) ^ ror32(x,22)) #define e1(x) (ror32(x, 6) ^ ror32(x,11) ^ ror32(x,25)) #define s0(x) (ror32(x, 7) ^ ror32(x,18) ^ (x >> 3)) #define s1(x) (ror32(x,17) ^ ror32(x,19) ^ (x >> 10)) #define H0 0x6a09e667 #define H1 0xbb67ae85 #define H2 0x3c6ef372 #define H3 0xa54ff53a #define H4 0x510e527f #define H5 0x9b05688c #define H6 0x1f83d9ab #define H7 0x5be0cd19 static inline uint32_t ror32(uint32_t word, unsigned int shift) { return (word >> shift) | (word << (32 - shift)); } static inline void LOAD_OP(int I, uint32_t *W, const uint8_t *input) { W[I] = __be32_to_cpu(((uint32_t *)(input))[I]); } static inline void BLEND_OP(int I, uint32_t *W) { W[I] = s1(W[I-2]) + W[I-7] + s0(W[I-15]) + W[I-16]; } static void sha256_transform(uint32_t *state, const uint8_t *input) { uint32_t a, b, c, d, e, f, g, h, t1, t2; uint32_t W[64]; int i; /* load the input */ for (i = 0; i < 16; i++) LOAD_OP(i, W, input); /* now blend */ for (i = 16; i < 64; i++) BLEND_OP(i, W); /* load the state into our registers */ a=state[0]; b=state[1]; c=state[2]; d=state[3]; e=state[4]; f=state[5]; g=state[6]; h=state[7]; /* now iterate */ t1 = h + e1(e) + Ch(e,f,g) + 0x428a2f98 + W[ 0]; t2 = e0(a) + Maj(a,b,c); d+=t1; h=t1+t2; t1 = g + e1(d) + Ch(d,e,f) + 0x71374491 + W[ 1]; t2 = e0(h) + Maj(h,a,b); c+=t1; g=t1+t2; t1 = f + e1(c) + Ch(c,d,e) + 0xb5c0fbcf + W[ 2]; t2 = e0(g) + Maj(g,h,a); b+=t1; f=t1+t2; t1 = e + e1(b) + Ch(b,c,d) + 0xe9b5dba5 + W[ 3]; t2 = e0(f) + Maj(f,g,h); a+=t1; e=t1+t2; t1 = d + e1(a) + Ch(a,b,c) + 0x3956c25b + W[ 4]; t2 = e0(e) + Maj(e,f,g); h+=t1; d=t1+t2; t1 = c + e1(h) + Ch(h,a,b) + 0x59f111f1 + W[ 5]; t2 = e0(d) + Maj(d,e,f); g+=t1; c=t1+t2; t1 = b + e1(g) + Ch(g,h,a) + 0x923f82a4 + W[ 6]; t2 = e0(c) + Maj(c,d,e); f+=t1; b=t1+t2; t1 = a + e1(f) + Ch(f,g,h) + 0xab1c5ed5 + W[ 7]; t2 = e0(b) + Maj(b,c,d); e+=t1; a=t1+t2; t1 = h + e1(e) + Ch(e,f,g) + 0xd807aa98 + W[ 8]; t2 = e0(a) + Maj(a,b,c); d+=t1; h=t1+t2; t1 = g + e1(d) + Ch(d,e,f) + 0x12835b01 + W[ 9]; t2 = e0(h) + Maj(h,a,b); c+=t1; g=t1+t2; t1 = f + e1(c) + Ch(c,d,e) + 0x243185be + W[10]; t2 = e0(g) + Maj(g,h,a); b+=t1; f=t1+t2; t1 = e + e1(b) + Ch(b,c,d) + 0x550c7dc3 + W[11]; t2 = e0(f) + Maj(f,g,h); a+=t1; e=t1+t2; t1 = d + e1(a) + Ch(a,b,c) + 0x72be5d74 + W[12]; t2 = e0(e) + Maj(e,f,g); h+=t1; d=t1+t2; t1 = c + e1(h) + Ch(h,a,b) + 0x80deb1fe + W[13]; t2 = e0(d) + Maj(d,e,f); g+=t1; c=t1+t2; t1 = b + e1(g) + Ch(g,h,a) + 0x9bdc06a7 + W[14]; t2 = e0(c) + Maj(c,d,e); f+=t1; b=t1+t2; t1 = a + e1(f) + Ch(f,g,h) + 0xc19bf174 + W[15]; t2 = e0(b) + Maj(b,c,d); e+=t1; a=t1+t2; t1 = h + e1(e) + Ch(e,f,g) + 0xe49b69c1 + W[16]; t2 = e0(a) + Maj(a,b,c); d+=t1; h=t1+t2; t1 = g + e1(d) + Ch(d,e,f) + 0xefbe4786 + W[17]; t2 = e0(h) + Maj(h,a,b); c+=t1; g=t1+t2; t1 = f + e1(c) + Ch(c,d,e) + 0x0fc19dc6 + W[18]; t2 = e0(g) + Maj(g,h,a); b+=t1; f=t1+t2; t1 = e + e1(b) + Ch(b,c,d) + 0x240ca1cc + W[19]; t2 = e0(f) + Maj(f,g,h); a+=t1; e=t1+t2; t1 = d + e1(a) + Ch(a,b,c) + 0x2de92c6f + W[20]; t2 = e0(e) + Maj(e,f,g); h+=t1; d=t1+t2; t1 = c + e1(h) + Ch(h,a,b) + 0x4a7484aa + W[21]; t2 = e0(d) + Maj(d,e,f); g+=t1; c=t1+t2; t1 = b + e1(g) + Ch(g,h,a) + 0x5cb0a9dc + W[22]; t2 = e0(c) + Maj(c,d,e); f+=t1; b=t1+t2; t1 = a + e1(f) + Ch(f,g,h) + 0x76f988da + W[23]; t2 = e0(b) + Maj(b,c,d); e+=t1; a=t1+t2; t1 = h + e1(e) + Ch(e,f,g) + 0x983e5152 + W[24]; t2 = e0(a) + Maj(a,b,c); d+=t1; h=t1+t2; t1 = g + e1(d) + Ch(d,e,f) + 0xa831c66d + W[25]; t2 = e0(h) + Maj(h,a,b); c+=t1; g=t1+t2; t1 = f + e1(c) + Ch(c,d,e) + 0xb00327c8 + W[26]; t2 = e0(g) + Maj(g,h,a); b+=t1; f=t1+t2; t1 = e + e1(b) + Ch(b,c,d) + 0xbf597fc7 + W[27]; t2 = e0(f) + Maj(f,g,h); a+=t1; e=t1+t2; t1 = d + e1(a) + Ch(a,b,c) + 0xc6e00bf3 + W[28]; t2 = e0(e) + Maj(e,f,g); h+=t1; d=t1+t2; t1 = c + e1(h) + Ch(h,a,b) + 0xd5a79147 + W[29]; t2 = e0(d) + Maj(d,e,f); g+=t1; c=t1+t2; t1 = b + e1(g) + Ch(g,h,a) + 0x06ca6351 + W[30]; t2 = e0(c) + Maj(c,d,e); f+=t1; b=t1+t2; t1 = a + e1(f) + Ch(f,g,h) + 0x14292967 + W[31]; t2 = e0(b) + Maj(b,c,d); e+=t1; a=t1+t2; t1 = h + e1(e) + Ch(e,f,g) + 0x27b70a85 + W[32]; t2 = e0(a) + Maj(a,b,c); d+=t1; h=t1+t2; t1 = g + e1(d) + Ch(d,e,f) + 0x2e1b2138 + W[33]; t2 = e0(h) + Maj(h,a,b); c+=t1; g=t1+t2; t1 = f + e1(c) + Ch(c,d,e) + 0x4d2c6dfc + W[34]; t2 = e0(g) + Maj(g,h,a); b+=t1; f=t1+t2; t1 = e + e1(b) + Ch(b,c,d) + 0x53380d13 + W[35]; t2 = e0(f) + Maj(f,g,h); a+=t1; e=t1+t2; t1 = d + e1(a) + Ch(a,b,c) + 0x650a7354 + W[36]; t2 = e0(e) + Maj(e,f,g); h+=t1; d=t1+t2; t1 = c + e1(h) + Ch(h,a,b) + 0x766a0abb + W[37]; t2 = e0(d) + Maj(d,e,f); g+=t1; c=t1+t2; t1 = b + e1(g) + Ch(g,h,a) + 0x81c2c92e + W[38]; t2 = e0(c) + Maj(c,d,e); f+=t1; b=t1+t2; t1 = a + e1(f) + Ch(f,g,h) + 0x92722c85 + W[39]; t2 = e0(b) + Maj(b,c,d); e+=t1; a=t1+t2; t1 = h + e1(e) + Ch(e,f,g) + 0xa2bfe8a1 + W[40]; t2 = e0(a) + Maj(a,b,c); d+=t1; h=t1+t2; t1 = g + e1(d) + Ch(d,e,f) + 0xa81a664b + W[41]; t2 = e0(h) + Maj(h,a,b); c+=t1; g=t1+t2; t1 = f + e1(c) + Ch(c,d,e) + 0xc24b8b70 + W[42]; t2 = e0(g) + Maj(g,h,a); b+=t1; f=t1+t2; t1 = e + e1(b) + Ch(b,c,d) + 0xc76c51a3 + W[43]; t2 = e0(f) + Maj(f,g,h); a+=t1; e=t1+t2; t1 = d + e1(a) + Ch(a,b,c) + 0xd192e819 + W[44]; t2 = e0(e) + Maj(e,f,g); h+=t1; d=t1+t2; t1 = c + e1(h) + Ch(h,a,b) + 0xd6990624 + W[45]; t2 = e0(d) + Maj(d,e,f); g+=t1; c=t1+t2; t1 = b + e1(g) + Ch(g,h,a) + 0xf40e3585 + W[46]; t2 = e0(c) + Maj(c,d,e); f+=t1; b=t1+t2; t1 = a + e1(f) + Ch(f,g,h) + 0x106aa070 + W[47]; t2 = e0(b) + Maj(b,c,d); e+=t1; a=t1+t2; t1 = h + e1(e) + Ch(e,f,g) + 0x19a4c116 + W[48]; t2 = e0(a) + Maj(a,b,c); d+=t1; h=t1+t2; t1 = g + e1(d) + Ch(d,e,f) + 0x1e376c08 + W[49]; t2 = e0(h) + Maj(h,a,b); c+=t1; g=t1+t2; t1 = f + e1(c) + Ch(c,d,e) + 0x2748774c + W[50]; t2 = e0(g) + Maj(g,h,a); b+=t1; f=t1+t2; t1 = e + e1(b) + Ch(b,c,d) + 0x34b0bcb5 + W[51]; t2 = e0(f) + Maj(f,g,h); a+=t1; e=t1+t2; t1 = d + e1(a) + Ch(a,b,c) + 0x391c0cb3 + W[52]; t2 = e0(e) + Maj(e,f,g); h+=t1; d=t1+t2; t1 = c + e1(h) + Ch(h,a,b) + 0x4ed8aa4a + W[53]; t2 = e0(d) + Maj(d,e,f); g+=t1; c=t1+t2; t1 = b + e1(g) + Ch(g,h,a) + 0x5b9cca4f + W[54]; t2 = e0(c) + Maj(c,d,e); f+=t1; b=t1+t2; t1 = a + e1(f) + Ch(f,g,h) + 0x682e6ff3 + W[55]; t2 = e0(b) + Maj(b,c,d); e+=t1; a=t1+t2; t1 = h + e1(e) + Ch(e,f,g) + 0x748f82ee + W[56]; t2 = e0(a) + Maj(a,b,c); d+=t1; h=t1+t2; t1 = g + e1(d) + Ch(d,e,f) + 0x78a5636f + W[57]; t2 = e0(h) + Maj(h,a,b); c+=t1; g=t1+t2; t1 = f + e1(c) + Ch(c,d,e) + 0x84c87814 + W[58]; t2 = e0(g) + Maj(g,h,a); b+=t1; f=t1+t2; t1 = e + e1(b) + Ch(b,c,d) + 0x8cc70208 + W[59]; t2 = e0(f) + Maj(f,g,h); a+=t1; e=t1+t2; t1 = d + e1(a) + Ch(a,b,c) + 0x90befffa + W[60]; t2 = e0(e) + Maj(e,f,g); h+=t1; d=t1+t2; t1 = c + e1(h) + Ch(h,a,b) + 0xa4506ceb + W[61]; t2 = e0(d) + Maj(d,e,f); g+=t1; c=t1+t2; t1 = b + e1(g) + Ch(g,h,a) + 0xbef9a3f7 + W[62]; t2 = e0(c) + Maj(c,d,e); f+=t1; b=t1+t2; t1 = a + e1(f) + Ch(f,g,h) + 0xc67178f2 + W[63]; t2 = e0(b) + Maj(b,c,d); e+=t1; a=t1+t2; state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; state[5] += f; state[6] += g; state[7] += h; /* clear any sensitive info... */ a = b = c = d = e = f = g = h = t1 = t2 = 0; memset(W, 0, 64 * sizeof(uint32_t)); } void fio_sha256_init(struct fio_sha256_ctx *sctx) { sctx->state[0] = H0; sctx->state[1] = H1; sctx->state[2] = H2; sctx->state[3] = H3; sctx->state[4] = H4; sctx->state[5] = H5; sctx->state[6] = H6; sctx->state[7] = H7; sctx->count[0] = sctx->count[1] = 0; } void fio_sha256_update(struct fio_sha256_ctx *sctx, const uint8_t *data, unsigned int len) { unsigned int i, idx, part_len; /* Compute number of bytes mod 128 */ idx = (unsigned int)((sctx->count[0] >> 3) & 0x3f); /* Update number of bits */ if ((sctx->count[0] += (len << 3)) < (len << 3)) { sctx->count[1]++; sctx->count[1] += (len >> 29); } part_len = 64 - idx; /* Transform as many times as possible. */ if (len >= part_len) { memcpy(&sctx->buf[idx], data, part_len); sha256_transform(sctx->state, sctx->buf); for (i = part_len; i + 63 < len; i += 64) sha256_transform(sctx->state, &data[i]); idx = 0; } else { i = 0; } /* Buffer remaining input */ memcpy(&sctx->buf[idx], &data[i], len-i); } fio-2.1.3/crc/sha256.h000066400000000000000000000004051222032232000142050ustar00rootroot00000000000000#ifndef FIO_SHA256_H #define FIO_SHA256_H struct fio_sha256_ctx { uint32_t count[2]; uint32_t state[8]; uint8_t *buf; }; void fio_sha256_init(struct fio_sha256_ctx *); void fio_sha256_update(struct fio_sha256_ctx *, const uint8_t *, unsigned int); #endif fio-2.1.3/crc/sha512.c000066400000000000000000000155411222032232000142020ustar00rootroot00000000000000/* SHA-512 code by Jean-Luc Cooke * * Copyright (c) Jean-Luc Cooke * Copyright (c) Andrew McDonald * Copyright (c) 2003 Kyle McMartin * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * */ #include #include #include "../lib/bswap.h" #include "sha512.h" #define SHA384_DIGEST_SIZE 48 #define SHA512_DIGEST_SIZE 64 #define SHA384_HMAC_BLOCK_SIZE 128 #define SHA512_HMAC_BLOCK_SIZE 128 static inline uint64_t Ch(uint64_t x, uint64_t y, uint64_t z) { return z ^ (x & (y ^ z)); } static inline uint64_t Maj(uint64_t x, uint64_t y, uint64_t z) { return (x & y) | (z & (x | y)); } static inline uint64_t RORuint64_t(uint64_t x, uint64_t y) { return (x >> y) | (x << (64 - y)); } static const uint64_t sha512_K[80] = { 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL, }; #define e0(x) (RORuint64_t(x,28) ^ RORuint64_t(x,34) ^ RORuint64_t(x,39)) #define e1(x) (RORuint64_t(x,14) ^ RORuint64_t(x,18) ^ RORuint64_t(x,41)) #define s0(x) (RORuint64_t(x, 1) ^ RORuint64_t(x, 8) ^ (x >> 7)) #define s1(x) (RORuint64_t(x,19) ^ RORuint64_t(x,61) ^ (x >> 6)) /* H* initial state for SHA-512 */ #define H0 0x6a09e667f3bcc908ULL #define H1 0xbb67ae8584caa73bULL #define H2 0x3c6ef372fe94f82bULL #define H3 0xa54ff53a5f1d36f1ULL #define H4 0x510e527fade682d1ULL #define H5 0x9b05688c2b3e6c1fULL #define H6 0x1f83d9abfb41bd6bULL #define H7 0x5be0cd19137e2179ULL /* H'* initial state for SHA-384 */ #define HP0 0xcbbb9d5dc1059ed8ULL #define HP1 0x629a292a367cd507ULL #define HP2 0x9159015a3070dd17ULL #define HP3 0x152fecd8f70e5939ULL #define HP4 0x67332667ffc00b31ULL #define HP5 0x8eb44a8768581511ULL #define HP6 0xdb0c2e0d64f98fa7ULL #define HP7 0x47b5481dbefa4fa4ULL static inline void LOAD_OP(int I, uint64_t *W, const uint8_t *input) { W[I] = __be64_to_cpu( ((uint64_t *)(input))[I] ); } static inline void BLEND_OP(int I, uint64_t *W) { W[I] = s1(W[I-2]) + W[I-7] + s0(W[I-15]) + W[I-16]; } static void sha512_transform(uint64_t *state, uint64_t *W, const uint8_t *input) { uint64_t a, b, c, d, e, f, g, h, t1, t2; int i; /* load the input */ for (i = 0; i < 16; i++) LOAD_OP(i, W, input); for (i = 16; i < 80; i++) BLEND_OP(i, W); /* load the state into our registers */ a=state[0]; b=state[1]; c=state[2]; d=state[3]; e=state[4]; f=state[5]; g=state[6]; h=state[7]; /* now iterate */ for (i=0; i<80; i+=8) { t1 = h + e1(e) + Ch(e,f,g) + sha512_K[i ] + W[i ]; t2 = e0(a) + Maj(a,b,c); d+=t1; h=t1+t2; t1 = g + e1(d) + Ch(d,e,f) + sha512_K[i+1] + W[i+1]; t2 = e0(h) + Maj(h,a,b); c+=t1; g=t1+t2; t1 = f + e1(c) + Ch(c,d,e) + sha512_K[i+2] + W[i+2]; t2 = e0(g) + Maj(g,h,a); b+=t1; f=t1+t2; t1 = e + e1(b) + Ch(b,c,d) + sha512_K[i+3] + W[i+3]; t2 = e0(f) + Maj(f,g,h); a+=t1; e=t1+t2; t1 = d + e1(a) + Ch(a,b,c) + sha512_K[i+4] + W[i+4]; t2 = e0(e) + Maj(e,f,g); h+=t1; d=t1+t2; t1 = c + e1(h) + Ch(h,a,b) + sha512_K[i+5] + W[i+5]; t2 = e0(d) + Maj(d,e,f); g+=t1; c=t1+t2; t1 = b + e1(g) + Ch(g,h,a) + sha512_K[i+6] + W[i+6]; t2 = e0(c) + Maj(c,d,e); f+=t1; b=t1+t2; t1 = a + e1(f) + Ch(f,g,h) + sha512_K[i+7] + W[i+7]; t2 = e0(b) + Maj(b,c,d); e+=t1; a=t1+t2; } state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; state[5] += f; state[6] += g; state[7] += h; /* erase our data */ a = b = c = d = e = f = g = h = t1 = t2 = 0; } void fio_sha512_init(struct fio_sha512_ctx *sctx) { sctx->state[0] = H0; sctx->state[1] = H1; sctx->state[2] = H2; sctx->state[3] = H3; sctx->state[4] = H4; sctx->state[5] = H5; sctx->state[6] = H6; sctx->state[7] = H7; sctx->count[0] = sctx->count[1] = sctx->count[2] = sctx->count[3] = 0; } void fio_sha512_update(struct fio_sha512_ctx *sctx, const uint8_t *data, unsigned int len) { unsigned int i, idx, part_len; /* Compute number of bytes mod 128 */ idx = (unsigned int)((sctx->count[0] >> 3) & 0x7F); /* Update number of bits */ if ((sctx->count[0] += (len << 3)) < (len << 3)) { if ((sctx->count[1] += 1) < 1) if ((sctx->count[2] += 1) < 1) sctx->count[3]++; sctx->count[1] += (len >> 29); } part_len = 128 - idx; /* Transform as many times as possible. */ if (len >= part_len) { memcpy(&sctx->buf[idx], data, part_len); sha512_transform(sctx->state, sctx->W, sctx->buf); for (i = part_len; i + 127 < len; i+=128) sha512_transform(sctx->state, sctx->W, &data[i]); idx = 0; } else { i = 0; } /* Buffer remaining input */ memcpy(&sctx->buf[idx], &data[i], len - i); /* erase our data */ memset(sctx->W, 0, sizeof(sctx->W)); } fio-2.1.3/crc/sha512.h000066400000000000000000000004261222032232000142030ustar00rootroot00000000000000#ifndef FIO_SHA512_H #define FIO_SHA512_H struct fio_sha512_ctx { uint64_t state[8]; uint32_t count[4]; uint8_t *buf; uint64_t W[80]; }; void fio_sha512_init(struct fio_sha512_ctx *); void fio_sha512_update(struct fio_sha512_ctx *, const uint8_t *, unsigned int); #endif fio-2.1.3/debug.c000066400000000000000000000007201222032232000135070ustar00rootroot00000000000000#include #include #include #include "debug.h" #ifdef FIO_INC_DEBUG void __dprint(int type, const char *str, ...) { va_list args; pid_t pid; assert(type < FD_DEBUG_MAX); pid = getpid(); if (fio_debug_jobp && *fio_debug_jobp != -1U && pid != *fio_debug_jobp) return; log_info("%-8s ", debug_levels[type].name); log_info("%-5u ", (int) pid); va_start(args, str); log_valist(str, args); va_end(args); } #endif fio-2.1.3/debug.h000066400000000000000000000015321222032232000135160ustar00rootroot00000000000000#ifndef FIO_DEBUG_H #define FIO_DEBUG_H #include #include "log.h" enum { FD_PROCESS = 0, FD_FILE, FD_IO, FD_MEM, FD_BLKTRACE, FD_VERIFY, FD_RANDOM, FD_PARSE, FD_DISKUTIL, FD_JOB, FD_MUTEX, FD_PROFILE, FD_TIME, FD_NET, FD_DEBUG_MAX, }; extern unsigned int fio_debug_jobno, *fio_debug_jobp; #ifdef FIO_INC_DEBUG struct debug_level { const char *name; const char *help; unsigned long shift; unsigned int jobno; }; extern struct debug_level debug_levels[]; extern unsigned long fio_debug; void __dprint(int type, const char *str, ...) __attribute__((format (printf, 2, 3))); #define dprint(type, str, args...) \ do { \ if ((((1 << type)) & fio_debug) == 0) \ break; \ __dprint((type), (str), ##args); \ } while (0) \ #else static inline void dprint(int type, const char *str, ...) { } #endif #endif fio-2.1.3/diskutil.c000066400000000000000000000402531222032232000142560ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "fio.h" #include "smalloc.h" #include "diskutil.h" static int last_majdev, last_mindev; static struct disk_util *last_du; static struct fio_mutex *disk_util_mutex; FLIST_HEAD(disk_list); static struct disk_util *__init_per_file_disk_util(struct thread_data *td, int majdev, int mindev, char *path); static void disk_util_free(struct disk_util *du) { if (du == last_du) last_du = NULL; while (!flist_empty(&du->slaves)) { struct disk_util *slave; slave = flist_entry(du->slaves.next, struct disk_util, slavelist); flist_del(&slave->slavelist); slave->users--; } fio_mutex_remove(du->lock); sfree(du); } static int get_io_ticks(struct disk_util *du, struct disk_util_stat *dus) { unsigned in_flight; unsigned long long sectors[2]; char line[256]; FILE *f; char *p; int ret; dprint(FD_DISKUTIL, "open stat file: %s\n", du->path); f = fopen(du->path, "r"); if (!f) return 1; p = fgets(line, sizeof(line), f); if (!p) { fclose(f); return 1; } dprint(FD_DISKUTIL, "%s: %s", du->path, p); ret = sscanf(p, "%u %u %llu %u %u %u %llu %u %u %u %u\n", &dus->ios[0], &dus->merges[0], §ors[0], &dus->ticks[0], &dus->ios[1], &dus->merges[1], §ors[1], &dus->ticks[1], &in_flight, &dus->io_ticks, &dus->time_in_queue); fclose(f); dprint(FD_DISKUTIL, "%s: stat read ok? %d\n", du->path, ret == 1); dus->sectors[0] = sectors[0]; dus->sectors[1] = sectors[1]; return ret != 11; } static void update_io_tick_disk(struct disk_util *du) { struct disk_util_stat __dus, *dus, *ldus; struct timeval t; if (!du->users) return; if (get_io_ticks(du, &__dus)) return; dus = &du->dus; ldus = &du->last_dus; dus->sectors[0] += (__dus.sectors[0] - ldus->sectors[0]); dus->sectors[1] += (__dus.sectors[1] - ldus->sectors[1]); dus->ios[0] += (__dus.ios[0] - ldus->ios[0]); dus->ios[1] += (__dus.ios[1] - ldus->ios[1]); dus->merges[0] += (__dus.merges[0] - ldus->merges[0]); dus->merges[1] += (__dus.merges[1] - ldus->merges[1]); dus->ticks[0] += (__dus.ticks[0] - ldus->ticks[0]); dus->ticks[1] += (__dus.ticks[1] - ldus->ticks[1]); dus->io_ticks += (__dus.io_ticks - ldus->io_ticks); dus->time_in_queue += (__dus.time_in_queue - ldus->time_in_queue); fio_gettime(&t, NULL); dus->msec += mtime_since(&du->time, &t); memcpy(&du->time, &t, sizeof(t)); memcpy(ldus, &__dus, sizeof(__dus)); } int update_io_ticks(void) { struct flist_head *entry; struct disk_util *du; int ret = 0; dprint(FD_DISKUTIL, "update io ticks\n"); fio_mutex_down(disk_util_mutex); if (!disk_util_exit) { flist_for_each(entry, &disk_list) { du = flist_entry(entry, struct disk_util, list); update_io_tick_disk(du); } } else ret = 1; fio_mutex_up(disk_util_mutex); return ret; } static struct disk_util *disk_util_exists(int major, int minor) { struct flist_head *entry; struct disk_util *du; fio_mutex_down(disk_util_mutex); flist_for_each(entry, &disk_list) { du = flist_entry(entry, struct disk_util, list); if (major == du->major && minor == du->minor) { fio_mutex_up(disk_util_mutex); return du; } } fio_mutex_up(disk_util_mutex); return NULL; } static int get_device_numbers(char *file_name, int *maj, int *min) { struct stat st; int majdev, mindev; char tempname[PATH_MAX], *p; if (!lstat(file_name, &st)) { if (S_ISBLK(st.st_mode)) { majdev = major(st.st_rdev); mindev = minor(st.st_rdev); } else if (S_ISCHR(st.st_mode)) { majdev = major(st.st_rdev); mindev = minor(st.st_rdev); if (fio_lookup_raw(st.st_rdev, &majdev, &mindev)) return -1; } else if (S_ISFIFO(st.st_mode)) return -1; else { majdev = major(st.st_dev); mindev = minor(st.st_dev); } } else { /* * must be a file, open "." in that path */ strncpy(tempname, file_name, PATH_MAX - 1); p = dirname(tempname); if (stat(p, &st)) { perror("disk util stat"); return -1; } majdev = major(st.st_dev); mindev = minor(st.st_dev); } *min = mindev; *maj = majdev; return 0; } static int read_block_dev_entry(char *path, int *maj, int *min) { char line[256], *p; FILE *f; f = fopen(path, "r"); if (!f) { perror("open path"); return 1; } p = fgets(line, sizeof(line), f); fclose(f); if (!p) return 1; if (sscanf(p, "%u:%u", maj, min) != 2) return 1; return 0; } static void find_add_disk_slaves(struct thread_data *td, char *path, struct disk_util *masterdu) { DIR *dirhandle = NULL; struct dirent *dirent = NULL; char slavesdir[PATH_MAX], temppath[PATH_MAX], slavepath[PATH_MAX]; struct disk_util *slavedu = NULL; int majdev, mindev; ssize_t linklen; sprintf(slavesdir, "%s/%s", path, "slaves"); dirhandle = opendir(slavesdir); if (!dirhandle) return; while ((dirent = readdir(dirhandle)) != NULL) { if (!strcmp(dirent->d_name, ".") || !strcmp(dirent->d_name, "..")) continue; sprintf(temppath, "%s%s%s", slavesdir, FIO_OS_PATH_SEPARATOR, dirent->d_name); /* Can we always assume that the slaves device entries * are links to the real directories for the slave * devices? */ linklen = readlink(temppath, slavepath, PATH_MAX - 0); if (linklen < 0) { perror("readlink() for slave device."); return; } slavepath[linklen] = '\0'; sprintf(temppath, "%s/%s/dev", slavesdir, slavepath); if (read_block_dev_entry(temppath, &majdev, &mindev)) { perror("Error getting slave device numbers."); return; } /* * See if this maj,min already exists */ slavedu = disk_util_exists(majdev, mindev); if (slavedu) continue; sprintf(temppath, "%s%s%s", slavesdir, FIO_OS_PATH_SEPARATOR, slavepath); __init_per_file_disk_util(td, majdev, mindev, temppath); slavedu = disk_util_exists(majdev, mindev); /* Should probably use an assert here. slavedu should * always be present at this point. */ if (slavedu) { slavedu->users++; flist_add_tail(&slavedu->slavelist, &masterdu->slaves); } } closedir(dirhandle); } static struct disk_util *disk_util_add(struct thread_data *td, int majdev, int mindev, char *path) { struct disk_util *du, *__du; struct flist_head *entry; int l; dprint(FD_DISKUTIL, "add maj/min %d/%d: %s\n", majdev, mindev, path); du = smalloc(sizeof(*du)); if (!du) { log_err("fio: smalloc() pool exhausted\n"); return NULL; } memset(du, 0, sizeof(*du)); INIT_FLIST_HEAD(&du->list); l = snprintf(du->path, sizeof(du->path), "%s/stat", path); if (l < 0 || l >= sizeof(du->path)) { log_err("constructed path \"%.100s[...]/stat\" larger than buffer (%zu bytes)\n", path, sizeof(du->path) - 1); sfree(du); return NULL; } strncpy((char *) du->dus.name, basename(path), FIO_DU_NAME_SZ); du->sysfs_root = path; du->major = majdev; du->minor = mindev; INIT_FLIST_HEAD(&du->slavelist); INIT_FLIST_HEAD(&du->slaves); du->lock = fio_mutex_init(FIO_MUTEX_UNLOCKED); du->users = 0; fio_mutex_down(disk_util_mutex); flist_for_each(entry, &disk_list) { __du = flist_entry(entry, struct disk_util, list); dprint(FD_DISKUTIL, "found %s in list\n", __du->dus.name); if (!strcmp((char *) du->dus.name, (char *) __du->dus.name)) { disk_util_free(du); fio_mutex_up(disk_util_mutex); return __du; } } dprint(FD_DISKUTIL, "add %s to list\n", du->dus.name); fio_gettime(&du->time, NULL); get_io_ticks(du, &du->last_dus); flist_add_tail(&du->list, &disk_list); fio_mutex_up(disk_util_mutex); find_add_disk_slaves(td, path, du); return du; } static int check_dev_match(int majdev, int mindev, char *path) { int major, minor; if (read_block_dev_entry(path, &major, &minor)) return 1; if (majdev == major && mindev == minor) return 0; return 1; } static int find_block_dir(int majdev, int mindev, char *path, int link_ok) { struct dirent *dir; struct stat st; int found = 0; DIR *D; D = opendir(path); if (!D) return 0; while ((dir = readdir(D)) != NULL) { char full_path[256]; if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")) continue; sprintf(full_path, "%s%s%s", path, FIO_OS_PATH_SEPARATOR, dir->d_name); if (!strcmp(dir->d_name, "dev")) { if (!check_dev_match(majdev, mindev, full_path)) { found = 1; break; } } if (link_ok) { if (stat(full_path, &st) == -1) { perror("stat"); break; } } else { if (lstat(full_path, &st) == -1) { perror("stat"); break; } } if (!S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) continue; found = find_block_dir(majdev, mindev, full_path, 0); if (found) { strcpy(path, full_path); break; } } closedir(D); return found; } static struct disk_util *__init_per_file_disk_util(struct thread_data *td, int majdev, int mindev, char *path) { struct stat st; char tmp[PATH_MAX]; char *p; /* * If there's a ../queue/ directory there, we are inside a partition. * Check if that is the case and jump back. For loop/md/dm etc we * are already in the right spot. */ sprintf(tmp, "%s/../queue", path); if (!stat(tmp, &st)) { p = dirname(path); sprintf(tmp, "%s/queue", p); if (stat(tmp, &st)) { log_err("unknown sysfs layout\n"); return NULL; } strncpy(tmp, p, PATH_MAX - 1); sprintf(path, "%s", tmp); } if (td->o.ioscheduler && !td->sysfs_root) td->sysfs_root = strdup(path); return disk_util_add(td, majdev, mindev, path); } static struct disk_util *init_per_file_disk_util(struct thread_data *td, char *filename) { char foo[PATH_MAX]; struct disk_util *du; int mindev, majdev; if (get_device_numbers(filename, &majdev, &mindev)) return NULL; dprint(FD_DISKUTIL, "%s belongs to maj/min %d/%d\n", filename, majdev, mindev); du = disk_util_exists(majdev, mindev); if (du) { if (td->o.ioscheduler && !td->sysfs_root) td->sysfs_root = strdup(du->sysfs_root); return du; } /* * for an fs without a device, we will repeatedly stat through * sysfs which can take oodles of time for thousands of files. so * cache the last lookup and compare with that before going through * everything again. */ if (mindev == last_mindev && majdev == last_majdev) return last_du; last_mindev = mindev; last_majdev = majdev; sprintf(foo, "/sys/block"); if (!find_block_dir(majdev, mindev, foo, 1)) return NULL; return __init_per_file_disk_util(td, majdev, mindev, foo); } static struct disk_util *__init_disk_util(struct thread_data *td, struct fio_file *f) { return init_per_file_disk_util(td, f->file_name); } void init_disk_util(struct thread_data *td) { struct fio_file *f; unsigned int i; if (!td->o.do_disk_util || (td->io_ops->flags & (FIO_DISKLESSIO | FIO_NODISKUTIL))) return; for_each_file(td, f, i) f->du = __init_disk_util(td, f); } static void show_agg_stats(struct disk_util_agg *agg, int terse) { if (!agg->slavecount) return; if (!terse) { log_info(", aggrios=%u/%u, aggrmerge=%u/%u, aggrticks=%u/%u," " aggrin_queue=%u, aggrutil=%3.2f%%", agg->ios[0] / agg->slavecount, agg->ios[1] / agg->slavecount, agg->merges[0] / agg->slavecount, agg->merges[1] / agg->slavecount, agg->ticks[0] / agg->slavecount, agg->ticks[1] / agg->slavecount, agg->time_in_queue / agg->slavecount, agg->max_util.u.f); } else { log_info(";slaves;%u;%u;%u;%u;%u;%u;%u;%3.2f%%", agg->ios[0] / agg->slavecount, agg->ios[1] / agg->slavecount, agg->merges[0] / agg->slavecount, agg->merges[1] / agg->slavecount, agg->ticks[0] / agg->slavecount, agg->ticks[1] / agg->slavecount, agg->time_in_queue / agg->slavecount, agg->max_util.u.f); } } static void aggregate_slaves_stats(struct disk_util *masterdu) { struct disk_util_agg *agg = &masterdu->agg; struct disk_util_stat *dus; struct flist_head *entry; struct disk_util *slavedu; double util; flist_for_each(entry, &masterdu->slaves) { slavedu = flist_entry(entry, struct disk_util, slavelist); dus = &slavedu->dus; agg->ios[0] += dus->ios[0]; agg->ios[1] += dus->ios[1]; agg->merges[0] += dus->merges[0]; agg->merges[1] += dus->merges[1]; agg->sectors[0] += dus->sectors[0]; agg->sectors[1] += dus->sectors[1]; agg->ticks[0] += dus->ticks[0]; agg->ticks[1] += dus->ticks[1]; agg->time_in_queue += dus->time_in_queue; agg->slavecount++; util = (double) (100 * dus->io_ticks / (double) slavedu->dus.msec); /* System utilization is the utilization of the * component with the highest utilization. */ if (util > agg->max_util.u.f) agg->max_util.u.f = util; } if (agg->max_util.u.f > 100.0) agg->max_util.u.f = 100.0; } void disk_util_prune_entries(void) { fio_mutex_down(disk_util_mutex); while (!flist_empty(&disk_list)) { struct disk_util *du; du = flist_entry(disk_list.next, struct disk_util, list); flist_del(&du->list); disk_util_free(du); } last_majdev = last_mindev = -1; fio_mutex_up(disk_util_mutex); fio_mutex_remove(disk_util_mutex); } void print_disk_util(struct disk_util_stat *dus, struct disk_util_agg *agg, int terse) { double util = 0; if (dus->msec) util = (double) 100 * dus->io_ticks / (double) dus->msec; if (util > 100.0) util = 100.0; if (!terse) { if (agg->slavecount) log_info(" "); log_info(" %s: ios=%u/%u, merge=%u/%u, ticks=%u/%u, " "in_queue=%u, util=%3.2f%%", dus->name, dus->ios[0], dus->ios[1], dus->merges[0], dus->merges[1], dus->ticks[0], dus->ticks[1], dus->time_in_queue, util); } else { log_info(";%s;%u;%u;%u;%u;%u;%u;%u;%3.2f%%", dus->name, dus->ios[0], dus->ios[1], dus->merges[0], dus->merges[1], dus->ticks[0], dus->ticks[1], dus->time_in_queue, util); } /* * If the device has slaves, aggregate the stats for * those slave devices also. */ show_agg_stats(agg, terse); if (!terse) log_info("\n"); } static void print_disk_util_json(struct disk_util *du, struct json_array *array) { double util = 0; struct disk_util_stat *dus = &du->dus; struct disk_util_agg *agg = &du->agg; struct json_object *obj; obj = json_create_object(); json_array_add_value_object(array, obj); if (dus->msec) util = (double) 100 * dus->io_ticks / (double) dus->msec; if (util > 100.0) util = 100.0; json_object_add_value_string(obj, "name", dus->name); json_object_add_value_int(obj, "read_ios", dus->ios[0]); json_object_add_value_int(obj, "write_ios", dus->ios[1]); json_object_add_value_int(obj, "read_merges", dus->merges[0]); json_object_add_value_int(obj, "write_merges", dus->merges[1]); json_object_add_value_int(obj, "read_ticks", dus->ticks[0]); json_object_add_value_int(obj, "write_ticks", dus->ticks[1]); json_object_add_value_int(obj, "in_queue", dus->time_in_queue); json_object_add_value_float(obj, "util", util); /* * If the device has slaves, aggregate the stats for * those slave devices also. */ if (!agg->slavecount) return; json_object_add_value_int(obj, "aggr_read_ios", agg->ios[0] / agg->slavecount); json_object_add_value_int(obj, "aggr_write_ios", agg->ios[1] / agg->slavecount); json_object_add_value_int(obj, "aggr_read_merges", agg->merges[0] / agg->slavecount); json_object_add_value_int(obj, "aggr_write_merge", agg->merges[1] / agg->slavecount); json_object_add_value_int(obj, "aggr_read_ticks", agg->ticks[0] / agg->slavecount); json_object_add_value_int(obj, "aggr_write_ticks", agg->ticks[1] / agg->slavecount); json_object_add_value_int(obj, "aggr_in_queue", agg->time_in_queue / agg->slavecount); json_object_add_value_float(obj, "aggr_util", agg->max_util.u.f); } void show_disk_util(int terse, struct json_object *parent) { struct flist_head *entry; struct disk_util *du; struct json_array *array = NULL; fio_mutex_down(disk_util_mutex); if (flist_empty(&disk_list)) { fio_mutex_up(disk_util_mutex); return; } if (!terse) log_info("\nDisk stats (read/write):\n"); if (output_format == FIO_OUTPUT_JSON) { array = json_create_array(); json_object_add_value_array(parent, "disk_util", array); } flist_for_each(entry, &disk_list) { du = flist_entry(entry, struct disk_util, list); aggregate_slaves_stats(du); if (output_format == FIO_OUTPUT_JSON) print_disk_util_json(du, array); else print_disk_util(&du->dus, &du->agg, terse); } fio_mutex_up(disk_util_mutex); } void setup_disk_util(void) { disk_util_mutex = fio_mutex_init(FIO_MUTEX_UNLOCKED); } fio-2.1.3/diskutil.h000066400000000000000000000053511222032232000142630ustar00rootroot00000000000000#ifndef FIO_DISKUTIL_H #define FIO_DISKUTIL_H #include "json.h" #define FIO_DU_NAME_SZ 64 extern volatile int disk_util_exit; /* * Disk utils as read in /sys/block//stat */ struct disk_util_stat { uint8_t name[FIO_DU_NAME_SZ]; uint32_t ios[2]; uint32_t merges[2]; uint64_t sectors[2]; uint32_t ticks[2]; uint32_t io_ticks; uint32_t time_in_queue; uint64_t msec; }; struct disk_util_agg { uint32_t ios[2]; uint32_t merges[2]; uint64_t sectors[2]; uint32_t ticks[2]; uint32_t io_ticks; uint32_t time_in_queue; uint32_t slavecount; fio_fp64_t max_util; }; /* * Per-device disk util management */ struct disk_util { struct flist_head list; /* If this disk is a slave, hook it into the master's * list using this head. */ struct flist_head slavelist; char *name; char *sysfs_root; char path[PATH_MAX]; int major, minor; struct disk_util_stat dus; struct disk_util_stat last_dus; struct disk_util_agg agg; /* For software raids, this entry maintains pointers to the * entries for the slave devices. The disk_util entries for * the slaves devices should primarily be maintained through * the disk_list list, i.e. for memory allocation and * de-allocation, etc. Whereas this list should be used only * for aggregating a software RAID's disk util figures. */ struct flist_head slaves; struct timeval time; struct fio_mutex *lock; unsigned long users; }; static inline void disk_util_mod(struct disk_util *du, int val) { if (du) { struct flist_head *n; fio_mutex_down(du->lock); du->users += val; flist_for_each(n, &du->slavelist) { struct disk_util *slave; slave = flist_entry(n, struct disk_util, slavelist); slave->users += val; } fio_mutex_up(du->lock); } } static inline void disk_util_inc(struct disk_util *du) { disk_util_mod(du, 1); } static inline void disk_util_dec(struct disk_util *du) { disk_util_mod(du, -1); } #define DISK_UTIL_MSEC (250) extern struct flist_head disk_list; extern void wait_for_disk_thread_exit(void); /* * disk util stuff */ #ifdef FIO_HAVE_DISK_UTIL extern void print_disk_util(struct disk_util_stat *, struct disk_util_agg *, int terse); extern void show_disk_util(int terse, struct json_object *parent); extern void init_disk_util(struct thread_data *); extern int update_io_ticks(void); extern void setup_disk_util(void); extern void disk_util_prune_entries(void); #else static inline void print_disk_util(struct disk_util_stat *du, struct disk_util_agg *agg, int terse) { } #define show_disk_util(terse, parent) #define disk_util_prune_entries() #define init_disk_util(td) #define setup_disk_util() static inline int update_io_ticks(void) { return disk_util_exit; } #endif static inline void disk_util_start_exit(void) { disk_util_exit = 1; } #endif fio-2.1.3/engines/000077500000000000000000000000001222032232000137065ustar00rootroot00000000000000fio-2.1.3/engines/binject.c000066400000000000000000000214261222032232000154750ustar00rootroot00000000000000/* * binject engine * * IO engine that uses the Linux binject interface to directly inject * bio's to block devices. * */ #include #include #include #include #include #include #include #include #include #include "../fio.h" #ifdef FIO_HAVE_BINJECT struct binject_data { struct b_user_cmd *cmds; struct io_u **events; struct pollfd *pfds; int *fd_flags; }; struct binject_file { unsigned int bs; int minor; int fd; }; static void binject_buc_init(struct binject_data *bd, struct io_u *io_u) { struct b_user_cmd *buc = &io_u->buc; memset(buc, 0, sizeof(*buc)); binject_buc_set_magic(buc); buc->buf = (unsigned long) io_u->xfer_buf; buc->len = io_u->xfer_buflen; buc->offset = io_u->offset; buc->usr_ptr = (unsigned long) io_u; buc->flags = B_FLAG_NOIDLE | B_FLAG_UNPLUG; assert(buc->buf); } static int pollin_events(struct pollfd *pfds, int fds) { int i; for (i = 0; i < fds; i++) if (pfds[i].revents & POLLIN) return 1; return 0; } static unsigned int binject_read_commands(struct thread_data *td, void *p, int left, int *err) { struct binject_file *bf; struct fio_file *f; int i, ret, events; one_more: events = 0; for_each_file(td, f, i) { bf = (struct binject_file *) (uintptr_t) f->engine_data; ret = read(bf->fd, p, left * sizeof(struct b_user_cmd)); if (ret < 0) { if (errno == EAGAIN) continue; *err = -errno; td_verror(td, errno, "read"); break; } else if (ret) { p += ret; events += ret / sizeof(struct b_user_cmd); } } if (*err || events) return events; usleep(1000); goto one_more; } static int fio_binject_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec fio_unused *t) { struct binject_data *bd = td->io_ops->data; int left = max, ret, r = 0, ev_index = 0; void *buf = bd->cmds; unsigned int i, events; struct fio_file *f; struct binject_file *bf; /* * Fill in the file descriptors */ for_each_file(td, f, i) { bf = (struct binject_file *) (uintptr_t) f->engine_data; /* * don't block for min events == 0 */ if (!min) { bd->fd_flags[i] = fcntl(bf->fd, F_GETFL); fcntl(bf->fd, F_SETFL, bd->fd_flags[i] | O_NONBLOCK); } bd->pfds[i].fd = bf->fd; bd->pfds[i].events = POLLIN; } while (left) { while (!min) { ret = poll(bd->pfds, td->o.nr_files, -1); if (ret < 0) { if (!r) r = -errno; td_verror(td, errno, "poll"); break; } else if (!ret) continue; if (pollin_events(bd->pfds, td->o.nr_files)) break; } if (r < 0) break; events = binject_read_commands(td, buf, left, &r); if (r < 0) break; left -= events; r += events; for (i = 0; i < events; i++) { struct b_user_cmd *buc = (struct b_user_cmd *) buf + i; bd->events[ev_index] = (struct io_u *) (unsigned long) buc->usr_ptr; ev_index++; } } if (!min) { for_each_file(td, f, i) { bf = (struct binject_file *) (uintptr_t) f->engine_data; fcntl(bf->fd, F_SETFL, bd->fd_flags[i]); } } if (r > 0) assert(ev_index == r); return r; } static int fio_binject_doio(struct thread_data *td, struct io_u *io_u) { struct b_user_cmd *buc = &io_u->buc; struct binject_file *bf = (struct binject_file *) (uintptr_t) io_u->file->engine_data; int ret; ret = write(bf->fd, buc, sizeof(*buc)); if (ret < 0) return ret; return FIO_Q_QUEUED; } static int fio_binject_prep(struct thread_data *td, struct io_u *io_u) { struct binject_data *bd = td->io_ops->data; struct b_user_cmd *buc = &io_u->buc; struct binject_file *bf = (struct binject_file *) (uintptr_t) io_u->file->engine_data; if (io_u->xfer_buflen & (bf->bs - 1)) { log_err("read/write not sector aligned\n"); return EINVAL; } if (io_u->ddir == DDIR_READ) { binject_buc_init(bd, io_u); buc->type = B_TYPE_READ; } else if (io_u->ddir == DDIR_WRITE) { binject_buc_init(bd, io_u); if (io_u->flags & IO_U_F_BARRIER) buc->type = B_TYPE_WRITEBARRIER; else buc->type = B_TYPE_WRITE; } else if (io_u->ddir == DDIR_TRIM) { binject_buc_init(bd, io_u); buc->type = B_TYPE_DISCARD; } else { assert(0); } return 0; } static int fio_binject_queue(struct thread_data *td, struct io_u *io_u) { int ret; fio_ro_check(td, io_u); ret = fio_binject_doio(td, io_u); if (ret < 0) io_u->error = errno; if (io_u->error) { td_verror(td, io_u->error, "xfer"); return FIO_Q_COMPLETED; } return ret; } static struct io_u *fio_binject_event(struct thread_data *td, int event) { struct binject_data *bd = td->io_ops->data; return bd->events[event]; } static int binject_open_ctl(struct thread_data *td) { int fd; fd = open("/dev/binject-ctl", O_RDWR); if (fd < 0) td_verror(td, errno, "open binject-ctl"); return fd; } static void binject_unmap_dev(struct thread_data *td, struct binject_file *bf) { struct b_ioctl_cmd bic; int fdb; if (bf->fd >= 0) { close(bf->fd); bf->fd = -1; } fdb = binject_open_ctl(td); if (fdb < 0) return; bic.minor = bf->minor; if (ioctl(fdb, B_IOCTL_DEL, &bic) < 0) td_verror(td, errno, "binject dev unmap"); close(fdb); } static int binject_map_dev(struct thread_data *td, struct binject_file *bf, int fd) { struct b_ioctl_cmd bic; char name[80]; struct stat sb; int fdb, dev_there, loops; fdb = binject_open_ctl(td); if (fdb < 0) return 1; bic.fd = fd; if (ioctl(fdb, B_IOCTL_ADD, &bic) < 0) { td_verror(td, errno, "binject dev map"); close(fdb); return 1; } bf->minor = bic.minor; sprintf(name, "/dev/binject%u", bf->minor); /* * Wait for udev to create the node... */ dev_there = loops = 0; do { if (!stat(name, &sb)) { dev_there = 1; break; } usleep(10000); } while (++loops < 100); close(fdb); if (!dev_there) { log_err("fio: timed out waiting for binject dev\n"); goto err_unmap; } bf->fd = open(name, O_RDWR); if (bf->fd < 0) { td_verror(td, errno, "binject dev open"); err_unmap: binject_unmap_dev(td, bf); return 1; } return 0; } static int fio_binject_close_file(struct thread_data *td, struct fio_file *f) { struct binject_file *bf = (struct binject_file *) (uintptr_t) f->engine_data; if (bf) { binject_unmap_dev(td, bf); free(bf); f->engine_data = 0; return generic_close_file(td, f); } return 0; } static int fio_binject_open_file(struct thread_data *td, struct fio_file *f) { struct binject_file *bf; unsigned int bs; int ret; ret = generic_open_file(td, f); if (ret) return 1; if (f->filetype != FIO_TYPE_BD) { log_err("fio: binject only works with block devices\n"); goto err_close; } if (ioctl(f->fd, BLKSSZGET, &bs) < 0) { td_verror(td, errno, "BLKSSZGET"); goto err_close; } bf = malloc(sizeof(*bf)); bf->bs = bs; bf->minor = bf->fd = -1; f->engine_data = (uintptr_t) bf; if (binject_map_dev(td, bf, f->fd)) { err_close: ret = generic_close_file(td, f); return 1; } return 0; } static void fio_binject_cleanup(struct thread_data *td) { struct binject_data *bd = td->io_ops->data; if (bd) { free(bd->events); free(bd->cmds); free(bd->fd_flags); free(bd->pfds); free(bd); } } static int fio_binject_init(struct thread_data *td) { struct binject_data *bd; bd = malloc(sizeof(*bd)); memset(bd, 0, sizeof(*bd)); bd->cmds = malloc(td->o.iodepth * sizeof(struct b_user_cmd)); memset(bd->cmds, 0, td->o.iodepth * sizeof(struct b_user_cmd)); bd->events = malloc(td->o.iodepth * sizeof(struct io_u *)); memset(bd->events, 0, td->o.iodepth * sizeof(struct io_u *)); bd->pfds = malloc(sizeof(struct pollfd) * td->o.nr_files); memset(bd->pfds, 0, sizeof(struct pollfd) * td->o.nr_files); bd->fd_flags = malloc(sizeof(int) * td->o.nr_files); memset(bd->fd_flags, 0, sizeof(int) * td->o.nr_files); td->io_ops->data = bd; return 0; } static struct ioengine_ops ioengine = { .name = "binject", .version = FIO_IOOPS_VERSION, .init = fio_binject_init, .prep = fio_binject_prep, .queue = fio_binject_queue, .getevents = fio_binject_getevents, .event = fio_binject_event, .cleanup = fio_binject_cleanup, .open_file = fio_binject_open_file, .close_file = fio_binject_close_file, .get_file_size = generic_get_file_size, .flags = FIO_RAWIO | FIO_BARRIER | FIO_MEMALIGN, }; #else /* FIO_HAVE_BINJECT */ /* * When we have a proper configure system in place, we simply wont build * and install this io engine. For now install a crippled version that * just complains and fails to load. */ static int fio_binject_init(struct thread_data fio_unused *td) { log_err("fio: ioengine binject not available\n"); return 1; } static struct ioengine_ops ioengine = { .name = "binject", .version = FIO_IOOPS_VERSION, .init = fio_binject_init, }; #endif static void fio_init fio_binject_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_binject_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/cpu.c000066400000000000000000000042061222032232000146430ustar00rootroot00000000000000/* * CPU engine * * Doesn't transfer any data, merely burns CPU cycles according to * the settings. * */ #include "../fio.h" struct cpu_options { struct thread_data *td; unsigned int cpuload; unsigned int cpucycle; }; static struct fio_option options[] = { { .name = "cpuload", .lname = "CPU load", .type = FIO_OPT_INT, .off1 = offsetof(struct cpu_options, cpuload), .help = "Use this percentage of CPU", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_INVALID, }, { .name = "cpuchunks", .lname = "CPU chunk", .type = FIO_OPT_INT, .off1 = offsetof(struct cpu_options, cpucycle), .help = "Length of the CPU burn cycles (usecs)", .def = "50000", .parent = "cpuload", .hide = 1, .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_INVALID, }, { .name = NULL, }, }; static int fio_cpuio_queue(struct thread_data *td, struct io_u fio_unused *io_u) { struct cpu_options *co = td->eo; usec_spin(co->cpucycle); return FIO_Q_COMPLETED; } static int fio_cpuio_init(struct thread_data *td) { struct thread_options *o = &td->o; struct cpu_options *co = td->eo; if (!co->cpuload) { td_vmsg(td, EINVAL, "cpu thread needs rate (cpuload=)","cpuio"); return 1; } if (co->cpuload > 100) co->cpuload = 100; /* * set thinktime_sleep and thinktime_spin appropriately */ o->thinktime_blocks = 1; o->thinktime_spin = 0; o->thinktime = (co->cpucycle * (100 - co->cpuload)) / co->cpuload; o->nr_files = o->open_files = 1; log_info("%s: ioengine=cpu, cpuload=%u, cpucycle=%u\n", td->o.name, co->cpuload, co->cpucycle); return 0; } static int fio_cpuio_open(struct thread_data fio_unused *td, struct fio_file fio_unused *f) { return 0; } static struct ioengine_ops ioengine = { .name = "cpuio", .version = FIO_IOOPS_VERSION, .queue = fio_cpuio_queue, .init = fio_cpuio_init, .open_file = fio_cpuio_open, .flags = FIO_SYNCIO | FIO_DISKLESSIO | FIO_NOIO, .options = options, .option_struct_size = sizeof(struct cpu_options), }; static void fio_init fio_cpuio_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_cpuio_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/e4defrag.c000066400000000000000000000112021222032232000155270ustar00rootroot00000000000000/* * ioe_e4defrag: ioengine for git://git.kernel.dk/fio.git * * IO engine that does regular EXT4_IOC_MOVE_EXT ioctls to simulate * defragment activity * */ #include #include #include #include #include #include #include #include #include #include "../fio.h" #ifndef EXT4_IOC_MOVE_EXT #define EXT4_IOC_MOVE_EXT _IOWR('f', 15, struct move_extent) struct move_extent { __u32 reserved; /* should be zero */ __u32 donor_fd; /* donor file descriptor */ __u64 orig_start; /* logical start offset in block for orig */ __u64 donor_start; /* logical start offset in block for donor */ __u64 len; /* block length to be moved */ __u64 moved_len; /* moved block length */ }; #endif struct e4defrag_data { int donor_fd; int bsz; }; struct e4defrag_options { struct thread_data *td; unsigned int inplace; char * donor_name; }; static struct fio_option options[] = { { .name = "donorname", .type = FIO_OPT_STR_STORE, .off1 = offsetof(struct e4defrag_options, donor_name), .help = "File used as a block donor", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_E4DEFRAG, }, { .name = "inplace", .type = FIO_OPT_INT, .off1 = offsetof(struct e4defrag_options, inplace), .minval = 0, .maxval = 1, .help = "Alloc and free space inside defrag event", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_E4DEFRAG, }, { .name = NULL, }, }; static int fio_e4defrag_init(struct thread_data *td) { int r, len = 0; struct e4defrag_options *o = td->eo; struct e4defrag_data *ed; struct stat stub; char donor_name[PATH_MAX]; if (!strlen(o->donor_name)) { log_err("'donorname' options required\n"); return 1; } ed = malloc(sizeof(*ed)); if (!ed) { td_verror(td, -ENOMEM, "io_queue_init"); return 1; } memset(ed, 0 ,sizeof(*ed)); if (td->o.directory) len = sprintf(donor_name, "%s/", td->o.directory); sprintf(donor_name + len, "%s", o->donor_name); ed->donor_fd = open(donor_name, O_CREAT|O_WRONLY, 0644); if (ed->donor_fd < 0) { td_verror(td, ed->donor_fd, "io_queue_init"); log_err("Can't open donor file %s err:%d", donor_name, ed->donor_fd); free(ed); return 1; } if (!o->inplace) { long long len = td->o.file_size_high - td->o.start_offset; r = fallocate(ed->donor_fd, 0, td->o.start_offset, len); if (r) goto err; } r = fstat(ed->donor_fd, &stub); if (r) goto err; ed->bsz = stub.st_blksize; td->io_ops->data = ed; return 0; err: td_verror(td, errno, "io_queue_init"); close(ed->donor_fd); free(ed); return 1; } static void fio_e4defrag_cleanup(struct thread_data *td) { struct e4defrag_data *ed = td->io_ops->data; if (ed) { if (ed->donor_fd >= 0) close(ed->donor_fd); free(ed); } } static int fio_e4defrag_queue(struct thread_data *td, struct io_u *io_u) { int ret; unsigned long long len; struct move_extent me; struct fio_file *f = io_u->file; struct e4defrag_data *ed = td->io_ops->data; struct e4defrag_options *o = td->eo; fio_ro_check(td, io_u); /* Theoretically defragmentation should not change data, but it * changes data layout. So this function handle only DDIR_WRITE * in order to satisfy strict read only access pattern */ if (io_u->ddir != DDIR_WRITE) { io_u->error = EINVAL; return FIO_Q_COMPLETED; } if (o->inplace) { ret = fallocate(ed->donor_fd, 0, io_u->offset, io_u->xfer_buflen); if (ret) goto out; } memset(&me, 0, sizeof(me)); me.donor_fd = ed->donor_fd; me.orig_start = io_u->offset / ed->bsz; me.donor_start = me.orig_start; len = (io_u->offset + io_u->xfer_buflen + ed->bsz -1); me.len = len / ed->bsz - me.orig_start; ret = ioctl(f->fd, EXT4_IOC_MOVE_EXT, &me); len = me.moved_len * ed->bsz; if (len > io_u->xfer_buflen) len = io_u->xfer_buflen; if (len != io_u->xfer_buflen) { io_u->resid = io_u->xfer_buflen - len; io_u->error = 0; } if (ret) io_u->error = errno; if (o->inplace) ret = ftruncate(ed->donor_fd, 0); out: if (ret && !io_u->error) io_u->error = errno; return FIO_Q_COMPLETED; } static struct ioengine_ops ioengine = { .name = "e4defrag", .version = FIO_IOOPS_VERSION, .init = fio_e4defrag_init, .queue = fio_e4defrag_queue, .open_file = generic_open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, .flags = FIO_SYNCIO, .cleanup = fio_e4defrag_cleanup, .options = options, .option_struct_size = sizeof(struct e4defrag_options), }; static void fio_init fio_syncio_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_syncio_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/falloc.c000066400000000000000000000047641222032232000153250ustar00rootroot00000000000000/* * falloc: ioengine for git://git.kernel.dk/fio.git * * IO engine that does regular fallocate to simulate data transfer * as fio ioengine. * DDIR_READ does fallocate(,mode = FALLOC_FL_KEEP_SIZE,) * DDIR_WRITE does fallocate(,mode = 0) : fallocate with size extention * DDIR_TRIM does fallocate(,mode = FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE) * */ #include #include #include #include #include #include #include #include "../fio.h" #include "../filehash.h" /* * generic_open_file is not appropriate because does not allow to perform * TRIM in to file */ int open_file(struct thread_data *td, struct fio_file *f) { int from_hash = 0; dprint(FD_FILE, "fd open %s\n", f->file_name); if (f->filetype != FIO_TYPE_FILE) { log_err("fio: only files are supported fallocate \n"); return 1; } if (!strcmp(f->file_name, "-")) { log_err("fio: can't read/write to stdin/out\n"); return 1; } open_again: from_hash = file_lookup_open(f, O_CREAT|O_RDWR); if (f->fd == -1) { char buf[FIO_VERROR_SIZE]; int __e = errno; snprintf(buf, sizeof(buf), "open(%s)", f->file_name); td_verror(td, __e, buf); } if (!from_hash && f->fd != -1) { if (add_file_hash(f)) { int fio_unused ret; /* * OK to ignore, we haven't done anything with it */ ret = generic_close_file(td, f); goto open_again; } } return 0; } #ifndef FALLOC_FL_KEEP_SIZE #define FALLOC_FL_KEEP_SIZE 0x01 /* default is extend size */ #endif #ifndef FALLOC_FL_PUNCH_HOLE #define FALLOC_FL_PUNCH_HOLE 0x02 /* de-allocates range */ #endif static int fio_fallocate_queue(struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; int ret; int flags = 0; fio_ro_check(td, io_u); if (io_u->ddir == DDIR_READ) flags = FALLOC_FL_KEEP_SIZE; else if (io_u->ddir == DDIR_WRITE) flags = 0; else if (io_u->ddir == DDIR_TRIM) flags = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; ret = fallocate(f->fd, flags, io_u->offset, io_u->xfer_buflen); if (ret) io_u->error = errno; return FIO_Q_COMPLETED; } static struct ioengine_ops ioengine = { .name = "falloc", .version = FIO_IOOPS_VERSION, .queue = fio_fallocate_queue, .open_file = open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, .flags = FIO_SYNCIO }; static void fio_init fio_syncio_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_syncio_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/fusion-aw.c000066400000000000000000000113651222032232000157700ustar00rootroot00000000000000/* * Custom fio(1) engine that submits synchronous atomic writes to file. * * Copyright (C) 2013 Fusion-io, Inc. * Author: Santhosh Kumar Koundinya (skoundinya@fusionio.com). * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; under version 2 of the License. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License version * 2 for more details. * * You should have received a copy of the GNU General Public License Version 2 * along with this program; if not see */ #include #include #include "../fio.h" #include #define NUM_ATOMIC_CAPABILITIES (5) struct fas_data { nvm_handle_t nvm_handle; size_t xfer_buf_align; size_t xfer_buflen_align; size_t xfer_buflen_max; size_t sector_size; }; static int queue(struct thread_data *td, struct io_u *io_u) { int rc; struct fas_data *d = (struct fas_data *) io_u->file->engine_data; if (io_u->ddir != DDIR_WRITE) { td_vmsg(td, EINVAL, "only writes supported", "io_u->ddir"); rc = -EINVAL; goto out; } if ((size_t) io_u->xfer_buf % d->xfer_buf_align) { td_vmsg(td, EINVAL, "unaligned data buffer", "io_u->xfer_buf"); rc = -EINVAL; goto out; } if (io_u->xfer_buflen % d->xfer_buflen_align) { td_vmsg(td, EINVAL, "unaligned data size", "io_u->xfer_buflen"); rc = -EINVAL; goto out; } if (io_u->xfer_buflen > d->xfer_buflen_max) { td_vmsg(td, EINVAL, "data too big", "io_u->xfer_buflen"); rc = -EINVAL; goto out; } rc = nvm_atomic_write(d->nvm_handle, (uint64_t) io_u->xfer_buf, io_u->xfer_buflen, io_u->offset / d->sector_size); if (rc == -1) { td_verror(td, errno, "nvm_atomic_write"); rc = -errno; goto out; } rc = FIO_Q_COMPLETED; out: if (rc < 0) io_u->error = -rc; return rc; } static int open_file(struct thread_data *td, struct fio_file *f) { int rc; int fio_unused close_file_rc; struct fas_data *d; nvm_version_t nvm_version; nvm_capability_t nvm_capability[NUM_ATOMIC_CAPABILITIES]; d = malloc(sizeof(*d)); if (!d) { td_verror(td, ENOMEM, "malloc"); rc = ENOMEM; goto error; } d->nvm_handle = -1; f->engine_data = (uintptr_t) d; rc = generic_open_file(td, f); if (rc) goto free_engine_data; /* Set the version of the library as seen when engine is compiled */ nvm_version.major = NVM_PRIMITIVES_API_MAJOR; nvm_version.minor = NVM_PRIMITIVES_API_MINOR; nvm_version.micro = NVM_PRIMITIVES_API_MICRO; d->nvm_handle = nvm_get_handle(f->fd, &nvm_version); if (d->nvm_handle == -1) { td_vmsg(td, errno, "nvm_get_handle failed", "nvm_get_handle"); rc = errno; goto close_file; } nvm_capability[0].cap_id = NVM_CAP_ATOMIC_WRITE_START_ALIGN_ID; nvm_capability[1].cap_id = NVM_CAP_ATOMIC_WRITE_MULTIPLICITY_ID; nvm_capability[2].cap_id = NVM_CAP_ATOMIC_WRITE_MAX_VECTOR_SIZE_ID; nvm_capability[3].cap_id = NVM_CAP_SECTOR_SIZE_ID; nvm_capability[4].cap_id = NVM_CAP_ATOMIC_MAX_IOV_ID; rc = nvm_get_capabilities(d->nvm_handle, nvm_capability, NUM_ATOMIC_CAPABILITIES, false); if (rc == -1) { td_vmsg(td, errno, "error in getting atomic write capabilities", "nvm_get_capabilities"); rc = errno; goto close_file; } else if (rc < NUM_ATOMIC_CAPABILITIES) { td_vmsg(td, EINVAL, "couldn't get all the atomic write capabilities" , "nvm_get_capabilities"); rc = ECANCELED; goto close_file; } /* Reset rc to 0 because we got all capabilities we needed */ rc = 0; d->xfer_buf_align = nvm_capability[0].cap_value; d->xfer_buflen_align = nvm_capability[1].cap_value; d->xfer_buflen_max = d->xfer_buflen_align * nvm_capability[2].cap_value * nvm_capability[4].cap_value; d->sector_size = nvm_capability[3].cap_value; out: return rc; close_file: close_file_rc = generic_close_file(td, f); free_engine_data: free(d); error: f->fd = -1; f->engine_data = 0; goto out; } static int close_file(struct thread_data *td, struct fio_file *f) { struct fas_data *d = (struct fas_data *) f->engine_data; if (d) { if (d->nvm_handle != -1) nvm_release_handle(d->nvm_handle); free(d); f->engine_data = 0; } return generic_close_file(td, f); } static struct ioengine_ops ioengine = { .name = "fusion-aw-sync", .version = FIO_IOOPS_VERSION, .queue = queue, .open_file = open_file, .close_file = close_file, .get_file_size = generic_get_file_size, .flags = FIO_SYNCIO | FIO_RAWIO | FIO_MEMALIGN }; static void fio_init fio_fusion_aw_init(void) { register_ioengine(&ioengine); } static void fio_exit fio_fusion_aw_exit(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/guasi.c000066400000000000000000000141221222032232000151620ustar00rootroot00000000000000/* * guasi engine * * IO engine using the GUASI library. * * Before running make. You'll need the GUASI lib as well: * * http://www.xmailserver.org/guasi-lib.html * */ #include #include #include #include #include #include "../fio.h" #define GFIO_MIN_THREADS 32 #ifndef GFIO_MAX_THREADS #define GFIO_MAX_THREADS 2000 #endif #include #include #ifdef GFIO_DEBUG #define GDBG_PRINT(a) printf a #else #define GDBG_PRINT(a) (void) 0 #endif struct guasi_data { guasi_t hctx; int max_reqs; guasi_req_t *reqs; struct io_u **io_us; int queued_nr; int reqs_nr; }; static int fio_guasi_prep(struct thread_data fio_unused *td, struct io_u *io_u) { GDBG_PRINT(("fio_guasi_prep(%p)\n", io_u)); io_u->greq = NULL; return 0; } static struct io_u *fio_guasi_event(struct thread_data *td, int event) { struct guasi_data *ld = td->io_ops->data; struct io_u *io_u; struct guasi_reqinfo rinf; GDBG_PRINT(("fio_guasi_event(%d)\n", event)); if (guasi_req_info(ld->reqs[event], &rinf) < 0) { log_err("guasi_req_info(%d) FAILED!\n", event); return NULL; } io_u = rinf.asid; io_u->error = EINPROGRESS; GDBG_PRINT(("fio_guasi_event(%d) -> %p\n", event, io_u)); if (rinf.status == GUASI_STATUS_COMPLETE) { io_u->error = rinf.result; if (io_u->ddir == DDIR_READ || io_u->ddir == DDIR_WRITE) { io_u->error = 0; if (rinf.result != (long) io_u->xfer_buflen) { if (rinf.result >= 0) io_u->resid = io_u->xfer_buflen - rinf.result; else io_u->error = rinf.error; } } } return io_u; } static int fio_guasi_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec *t) { struct guasi_data *ld = td->io_ops->data; int n, r; long timeo = -1; GDBG_PRINT(("fio_guasi_getevents(%d, %d)\n", min, max)); if (min > ld->max_reqs) min = ld->max_reqs; if (max > ld->max_reqs) max = ld->max_reqs; if (t) timeo = t->tv_sec * 1000L + t->tv_nsec / 1000000L; for (n = 0; n < ld->reqs_nr; n++) guasi_req_free(ld->reqs[n]); n = 0; do { r = guasi_fetch(ld->hctx, ld->reqs + n, min - n, max - n, timeo); if (r < 0) { log_err("guasi_fetch() FAILED! (%d)\n", r); break; } n += r; if (n >= min) break; } while (1); ld->reqs_nr = n; GDBG_PRINT(("fio_guasi_getevents() -> %d\n", n)); return n; } static int fio_guasi_queue(struct thread_data *td, struct io_u *io_u) { struct guasi_data *ld = td->io_ops->data; fio_ro_check(td, io_u); GDBG_PRINT(("fio_guasi_queue(%p)\n", io_u)); if (ld->queued_nr == (int) td->o.iodepth) return FIO_Q_BUSY; ld->io_us[ld->queued_nr] = io_u; ld->queued_nr++; return FIO_Q_QUEUED; } static void fio_guasi_queued(struct thread_data *td, struct io_u **io_us, int nr) { int i; struct io_u *io_u; struct timeval now; if (!fio_fill_issue_time(td)) return; io_u_mark_submit(td, nr); fio_gettime(&now, NULL); for (i = 0; i < nr; i++) { io_u = io_us[i]; memcpy(&io_u->issue_time, &now, sizeof(now)); io_u_queued(td, io_u); } } static int fio_guasi_commit(struct thread_data *td) { struct guasi_data *ld = td->io_ops->data; int i; struct io_u *io_u; struct fio_file *f; GDBG_PRINT(("fio_guasi_commit(%d)\n", ld->queued_nr)); for (i = 0; i < ld->queued_nr; i++) { io_u = ld->io_us[i]; GDBG_PRINT(("fio_guasi_commit(%d) --> %p\n", i, io_u)); f = io_u->file; io_u->greq = NULL; if (io_u->ddir == DDIR_READ) io_u->greq = guasi__pread(ld->hctx, ld, io_u, 0, f->fd, io_u->xfer_buf, io_u->xfer_buflen, io_u->offset); else if (io_u->ddir == DDIR_WRITE) io_u->greq = guasi__pwrite(ld->hctx, ld, io_u, 0, f->fd, io_u->xfer_buf, io_u->xfer_buflen, io_u->offset); else if (ddir_sync(io_u->ddir)) io_u->greq = guasi__fsync(ld->hctx, ld, io_u, 0, f->fd); else { log_err("fio_guasi_commit() FAILED: unknow request %d\n", io_u->ddir); } if (io_u->greq == NULL) { log_err("fio_guasi_commit() FAILED: submit failed (%s)\n", strerror(errno)); return -1; } } fio_guasi_queued(td, ld->io_us, i); ld->queued_nr = 0; GDBG_PRINT(("fio_guasi_commit() -> %d\n", i)); return 0; } static int fio_guasi_cancel(struct thread_data fio_unused *td, struct io_u *io_u) { GDBG_PRINT(("fio_guasi_cancel(%p) req=%p\n", io_u, io_u->greq)); if (io_u->greq != NULL) guasi_req_cancel(io_u->greq); return 0; } static void fio_guasi_cleanup(struct thread_data *td) { struct guasi_data *ld = td->io_ops->data; int n; GDBG_PRINT(("fio_guasi_cleanup(%p)\n", ld)); if (ld) { for (n = 0; n < ld->reqs_nr; n++) guasi_req_free(ld->reqs[n]); guasi_free(ld->hctx); free(ld->reqs); free(ld->io_us); free(ld); } GDBG_PRINT(("fio_guasi_cleanup(%p) DONE\n", ld)); } static int fio_guasi_init(struct thread_data *td) { int maxthr; struct guasi_data *ld = malloc(sizeof(*ld)); GDBG_PRINT(("fio_guasi_init(): depth=%d\n", td->o.iodepth)); memset(ld, 0, sizeof(*ld)); maxthr = td->o.iodepth > GFIO_MIN_THREADS ? td->o.iodepth: GFIO_MIN_THREADS; if (maxthr > GFIO_MAX_THREADS) maxthr = GFIO_MAX_THREADS; if ((ld->hctx = guasi_create(GFIO_MIN_THREADS, maxthr, 1)) == NULL) { td_verror(td, errno, "guasi_create"); free(ld); return 1; } ld->max_reqs = td->o.iodepth; ld->reqs = malloc(ld->max_reqs * sizeof(guasi_req_t)); ld->io_us = malloc(ld->max_reqs * sizeof(struct io_u *)); memset(ld->io_us, 0, ld->max_reqs * sizeof(struct io_u *)); ld->queued_nr = 0; ld->reqs_nr = 0; td->io_ops->data = ld; GDBG_PRINT(("fio_guasi_init(): depth=%d -> %p\n", td->o.iodepth, ld)); return 0; } static struct ioengine_ops ioengine = { .name = "guasi", .version = FIO_IOOPS_VERSION, .init = fio_guasi_init, .prep = fio_guasi_prep, .queue = fio_guasi_queue, .commit = fio_guasi_commit, .cancel = fio_guasi_cancel, .getevents = fio_guasi_getevents, .event = fio_guasi_event, .cleanup = fio_guasi_cleanup, .open_file = generic_open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, }; static void fio_init fio_guasi_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_guasi_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/libaio.c000066400000000000000000000160201222032232000153100ustar00rootroot00000000000000/* * libaio engine * * IO engine using the Linux native aio interface. * */ #include #include #include #include #include #include #include "../fio.h" struct libaio_data { io_context_t aio_ctx; struct io_event *aio_events; struct iocb **iocbs; struct io_u **io_us; int iocbs_nr; }; struct libaio_options { struct thread_data *td; unsigned int userspace_reap; }; static struct fio_option options[] = { { .name = "userspace_reap", .lname = "Libaio userspace reaping", .type = FIO_OPT_STR_SET, .off1 = offsetof(struct libaio_options, userspace_reap), .help = "Use alternative user-space reap implementation", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_LIBAIO, }, { .name = NULL, }, }; static int fio_libaio_prep(struct thread_data fio_unused *td, struct io_u *io_u) { struct fio_file *f = io_u->file; if (io_u->ddir == DDIR_READ) io_prep_pread(&io_u->iocb, f->fd, io_u->xfer_buf, io_u->xfer_buflen, io_u->offset); else if (io_u->ddir == DDIR_WRITE) io_prep_pwrite(&io_u->iocb, f->fd, io_u->xfer_buf, io_u->xfer_buflen, io_u->offset); else if (ddir_sync(io_u->ddir)) io_prep_fsync(&io_u->iocb, f->fd); return 0; } static struct io_u *fio_libaio_event(struct thread_data *td, int event) { struct libaio_data *ld = td->io_ops->data; struct io_event *ev; struct io_u *io_u; ev = ld->aio_events + event; io_u = container_of(ev->obj, struct io_u, iocb); if (ev->res != io_u->xfer_buflen) { if (ev->res > io_u->xfer_buflen) io_u->error = -ev->res; else io_u->resid = io_u->xfer_buflen - ev->res; } else io_u->error = 0; return io_u; } struct aio_ring { unsigned id; /** kernel internal index number */ unsigned nr; /** number of io_events */ unsigned head; unsigned tail; unsigned magic; unsigned compat_features; unsigned incompat_features; unsigned header_length; /** size of aio_ring */ struct io_event events[0]; }; #define AIO_RING_MAGIC 0xa10a10a1 static int user_io_getevents(io_context_t aio_ctx, unsigned int max, struct io_event *events) { long i = 0; unsigned head; struct aio_ring *ring = (struct aio_ring*) aio_ctx; while (i < max) { head = ring->head; if (head == ring->tail) { /* There are no more completions */ break; } else { /* There is another completion to reap */ events[i] = ring->events[head]; read_barrier(); ring->head = (head + 1) % ring->nr; i++; } } return i; } static int fio_libaio_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec *t) { struct libaio_data *ld = td->io_ops->data; struct libaio_options *o = td->eo; unsigned actual_min = td->o.iodepth_batch_complete == 0 ? 0 : min; int r, events = 0; do { if (o->userspace_reap == 1 && actual_min == 0 && ((struct aio_ring *)(ld->aio_ctx))->magic == AIO_RING_MAGIC) { r = user_io_getevents(ld->aio_ctx, max, ld->aio_events + events); } else { r = io_getevents(ld->aio_ctx, actual_min, max, ld->aio_events + events, t); } if (r >= 0) events += r; else if (r == -EAGAIN) usleep(100); } while (events < min); return r < 0 ? r : events; } static int fio_libaio_queue(struct thread_data *td, struct io_u *io_u) { struct libaio_data *ld = td->io_ops->data; fio_ro_check(td, io_u); if (ld->iocbs_nr == (int) td->o.iodepth) return FIO_Q_BUSY; /* * fsync is tricky, since it can fail and we need to do it * serialized with other io. the reason is that linux doesn't * support aio fsync yet. So return busy for the case where we * have pending io, to let fio complete those first. */ if (ddir_sync(io_u->ddir)) { if (ld->iocbs_nr) return FIO_Q_BUSY; do_io_u_sync(td, io_u); return FIO_Q_COMPLETED; } if (io_u->ddir == DDIR_TRIM) { if (ld->iocbs_nr) return FIO_Q_BUSY; do_io_u_trim(td, io_u); return FIO_Q_COMPLETED; } ld->iocbs[ld->iocbs_nr] = &io_u->iocb; ld->io_us[ld->iocbs_nr] = io_u; ld->iocbs_nr++; return FIO_Q_QUEUED; } static void fio_libaio_queued(struct thread_data *td, struct io_u **io_us, unsigned int nr) { struct timeval now; unsigned int i; if (!fio_fill_issue_time(td)) return; fio_gettime(&now, NULL); for (i = 0; i < nr; i++) { struct io_u *io_u = io_us[i]; memcpy(&io_u->issue_time, &now, sizeof(now)); io_u_queued(td, io_u); } } static int fio_libaio_commit(struct thread_data *td) { struct libaio_data *ld = td->io_ops->data; struct iocb **iocbs; struct io_u **io_us; int ret; if (!ld->iocbs_nr) return 0; io_us = ld->io_us; iocbs = ld->iocbs; do { ret = io_submit(ld->aio_ctx, ld->iocbs_nr, iocbs); if (ret > 0) { fio_libaio_queued(td, io_us, ret); io_u_mark_submit(td, ret); ld->iocbs_nr -= ret; io_us += ret; iocbs += ret; ret = 0; } else if (!ret || ret == -EAGAIN || ret == -EINTR) { if (!ret) io_u_mark_submit(td, ret); continue; } else break; } while (ld->iocbs_nr); return ret; } static int fio_libaio_cancel(struct thread_data *td, struct io_u *io_u) { struct libaio_data *ld = td->io_ops->data; return io_cancel(ld->aio_ctx, &io_u->iocb, ld->aio_events); } static void fio_libaio_cleanup(struct thread_data *td) { struct libaio_data *ld = td->io_ops->data; if (ld) { io_destroy(ld->aio_ctx); free(ld->aio_events); free(ld->iocbs); free(ld->io_us); free(ld); } } static int fio_libaio_init(struct thread_data *td) { struct libaio_data *ld = malloc(sizeof(*ld)); struct libaio_options *o = td->eo; int err = 0; memset(ld, 0, sizeof(*ld)); /* * First try passing in 0 for queue depth, since we don't * care about the user ring. If that fails, the kernel is too old * and we need the right depth. */ if (!o->userspace_reap) err = io_queue_init(INT_MAX, &ld->aio_ctx); if (o->userspace_reap || err == -EINVAL) err = io_queue_init(td->o.iodepth, &ld->aio_ctx); if (err) { td_verror(td, -err, "io_queue_init"); log_err("fio: check /proc/sys/fs/aio-max-nr\n"); free(ld); return 1; } ld->aio_events = malloc(td->o.iodepth * sizeof(struct io_event)); memset(ld->aio_events, 0, td->o.iodepth * sizeof(struct io_event)); ld->iocbs = malloc(td->o.iodepth * sizeof(struct iocb *)); memset(ld->iocbs, 0, sizeof(struct iocb *)); ld->io_us = malloc(td->o.iodepth * sizeof(struct io_u *)); memset(ld->io_us, 0, td->o.iodepth * sizeof(struct io_u *)); ld->iocbs_nr = 0; td->io_ops->data = ld; return 0; } static struct ioengine_ops ioengine = { .name = "libaio", .version = FIO_IOOPS_VERSION, .init = fio_libaio_init, .prep = fio_libaio_prep, .queue = fio_libaio_queue, .commit = fio_libaio_commit, .cancel = fio_libaio_cancel, .getevents = fio_libaio_getevents, .event = fio_libaio_event, .cleanup = fio_libaio_cleanup, .open_file = generic_open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, .options = options, .option_struct_size = sizeof(struct libaio_options), }; static void fio_init fio_libaio_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_libaio_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/mmap.c000066400000000000000000000114261222032232000150100ustar00rootroot00000000000000/* * mmap engine * * IO engine that reads/writes from files by doing memcpy to/from * a memory mapped region of the file. * */ #include #include #include #include #include #include "../fio.h" #include "../verify.h" /* * Limits us to 1GB of mapped files in total */ #define MMAP_TOTAL_SZ (1 * 1024 * 1024 * 1024UL) static unsigned long mmap_map_size; static unsigned long mmap_map_mask; static int fio_mmap_file(struct thread_data *td, struct fio_file *f, size_t length, off_t off) { int flags = 0; if (td_rw(td)) flags = PROT_READ | PROT_WRITE; else if (td_write(td)) { flags = PROT_WRITE; if (td->o.verify != VERIFY_NONE) flags |= PROT_READ; } else flags = PROT_READ; f->mmap_ptr = mmap(NULL, length, flags, MAP_SHARED, f->fd, off); if (f->mmap_ptr == MAP_FAILED) { f->mmap_ptr = NULL; td_verror(td, errno, "mmap"); goto err; } if (!td_random(td)) { if (posix_madvise(f->mmap_ptr, length, POSIX_MADV_SEQUENTIAL) < 0) { td_verror(td, errno, "madvise"); goto err; } } else { if (posix_madvise(f->mmap_ptr, length, POSIX_MADV_RANDOM) < 0) { td_verror(td, errno, "madvise"); goto err; } } err: if (td->error && f->mmap_ptr) munmap(f->mmap_ptr, length); return td->error; } /* * Just mmap an appropriate portion, we cannot mmap the full extent */ static int fio_mmapio_prep_limited(struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; if (io_u->buflen > mmap_map_size) { log_err("fio: bs too big for mmap engine\n"); return EIO; } f->mmap_sz = mmap_map_size; if (f->mmap_sz > f->io_size) f->mmap_sz = f->io_size; f->mmap_off = io_u->offset; return fio_mmap_file(td, f, f->mmap_sz, f->mmap_off); } /* * Attempt to mmap the entire file */ static int fio_mmapio_prep_full(struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; int ret; if (fio_file_partial_mmap(f)) return EINVAL; f->mmap_sz = f->io_size; f->mmap_off = 0; ret = fio_mmap_file(td, f, f->mmap_sz, f->mmap_off); if (ret) fio_file_set_partial_mmap(f); return ret; } static int fio_mmapio_prep(struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; int ret; /* * It fits within existing mapping, use it */ if (io_u->offset >= f->mmap_off && io_u->offset + io_u->buflen < f->mmap_off + f->mmap_sz) goto done; /* * unmap any existing mapping */ if (f->mmap_ptr) { if (munmap(f->mmap_ptr, f->mmap_sz) < 0) return errno; f->mmap_ptr = NULL; } if (fio_mmapio_prep_full(td, io_u)) { td_clear_error(td); ret = fio_mmapio_prep_limited(td, io_u); if (ret) return ret; } done: io_u->mmap_data = f->mmap_ptr + io_u->offset - f->mmap_off - f->file_offset; return 0; } static int fio_mmapio_queue(struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; fio_ro_check(td, io_u); if (io_u->ddir == DDIR_READ) memcpy(io_u->xfer_buf, io_u->mmap_data, io_u->xfer_buflen); else if (io_u->ddir == DDIR_WRITE) memcpy(io_u->mmap_data, io_u->xfer_buf, io_u->xfer_buflen); else if (ddir_sync(io_u->ddir)) { if (msync(f->mmap_ptr, f->mmap_sz, MS_SYNC)) { io_u->error = errno; td_verror(td, io_u->error, "msync"); } } else if (io_u->ddir == DDIR_TRIM) { int ret = do_io_u_trim(td, io_u); if (!ret) td_verror(td, io_u->error, "trim"); } /* * not really direct, but should drop the pages from the cache */ if (td->o.odirect && ddir_rw(io_u->ddir)) { if (msync(io_u->mmap_data, io_u->xfer_buflen, MS_SYNC) < 0) { io_u->error = errno; td_verror(td, io_u->error, "msync"); } if (posix_madvise(io_u->mmap_data, io_u->xfer_buflen, POSIX_MADV_DONTNEED) < 0) { io_u->error = errno; td_verror(td, io_u->error, "madvise"); } } return FIO_Q_COMPLETED; } static int fio_mmapio_init(struct thread_data *td) { struct thread_options *o = &td->o; unsigned long shift, mask; if ((td->o.rw_min_bs & page_mask) && (o->odirect || o->fsync_blocks || o->fdatasync_blocks)) { log_err("fio: mmap options dictate a minimum block size of " "%llu bytes\n", (unsigned long long) page_size); return 1; } mmap_map_size = MMAP_TOTAL_SZ / td->o.nr_files; mask = mmap_map_size; shift = 0; do { mask >>= 1; if (!mask) break; shift++; } while (1); mmap_map_mask = 1UL << shift; return 0; } static struct ioengine_ops ioengine = { .name = "mmap", .version = FIO_IOOPS_VERSION, .init = fio_mmapio_init, .prep = fio_mmapio_prep, .queue = fio_mmapio_queue, .open_file = generic_open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, .flags = FIO_SYNCIO | FIO_NOEXTEND, }; static void fio_init fio_mmapio_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_mmapio_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/net.c000066400000000000000000000565021222032232000146500ustar00rootroot00000000000000/* * net engine * * IO engine that reads/writes to/from sockets. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../fio.h" struct netio_data { int listenfd; int use_splice; int pipes[2]; struct sockaddr_in addr; struct sockaddr_un addr_un; }; struct netio_options { struct thread_data *td; unsigned int port; unsigned int proto; unsigned int listen; unsigned int pingpong; unsigned int nodelay; unsigned int ttl; char * interface; }; struct udp_close_msg { uint32_t magic; uint32_t cmd; }; enum { FIO_LINK_CLOSE = 0x89, FIO_LINK_OPEN_CLOSE_MAGIC = 0x6c696e6b, FIO_LINK_OPEN = 0x98, FIO_TYPE_TCP = 1, FIO_TYPE_UDP = 2, FIO_TYPE_UNIX = 3, }; static int str_hostname_cb(void *data, const char *input); static struct fio_option options[] = { { .name = "hostname", .lname = "net engine hostname", .type = FIO_OPT_STR_STORE, .cb = str_hostname_cb, .help = "Hostname for net IO engine", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_NETIO, }, { .name = "port", .lname = "net engine port", .type = FIO_OPT_INT, .off1 = offsetof(struct netio_options, port), .minval = 1, .maxval = 65535, .help = "Port to use for TCP or UDP net connections", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_NETIO, }, { .name = "protocol", .lname = "net engine protocol", .alias = "proto", .type = FIO_OPT_STR, .off1 = offsetof(struct netio_options, proto), .help = "Network protocol to use", .def = "tcp", .posval = { { .ival = "tcp", .oval = FIO_TYPE_TCP, .help = "Transmission Control Protocol", }, { .ival = "udp", .oval = FIO_TYPE_UDP, .help = "User Datagram Protocol", }, { .ival = "unix", .oval = FIO_TYPE_UNIX, .help = "UNIX domain socket", }, }, .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_NETIO, }, #ifdef CONFIG_TCP_NODELAY { .name = "nodelay", .type = FIO_OPT_BOOL, .off1 = offsetof(struct netio_options, nodelay), .help = "Use TCP_NODELAY on TCP connections", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_NETIO, }, #endif { .name = "listen", .lname = "net engine listen", .type = FIO_OPT_STR_SET, .off1 = offsetof(struct netio_options, listen), .help = "Listen for incoming TCP connections", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_NETIO, }, { .name = "pingpong", .type = FIO_OPT_STR_SET, .off1 = offsetof(struct netio_options, pingpong), .help = "Ping-pong IO requests", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_NETIO, }, { .name = "interface", .lname = "net engine interface", .type = FIO_OPT_STR_STORE, .off1 = offsetof(struct netio_options, interface), .help = "Network interface to use", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_NETIO, }, { .name = "ttl", .lname = "net engine multicast ttl", .type = FIO_OPT_INT, .off1 = offsetof(struct netio_options, ttl), .def = "1", .minval = 0, .help = "Time-to-live value for outgoing UDP multicast packets", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_NETIO, }, { .name = NULL, }, }; /* * Return -1 for error and 'nr events' for a positive number * of events */ static int poll_wait(struct thread_data *td, int fd, short events) { struct pollfd pfd; int ret; while (!td->terminate) { pfd.fd = fd; pfd.events = events; ret = poll(&pfd, 1, -1); if (ret < 0) { if (errno == EINTR) break; td_verror(td, errno, "poll"); return -1; } else if (!ret) continue; break; } if (pfd.revents & events) return 1; return -1; } static int fio_netio_is_multicast(const char *mcaddr) { in_addr_t addr = inet_network(mcaddr); if (addr == -1) return 0; if (inet_network("224.0.0.0") <= addr && inet_network("239.255.255.255") >= addr) return 1; return 0; } static int fio_netio_prep(struct thread_data *td, struct io_u *io_u) { struct netio_options *o = td->eo; /* * Make sure we don't see spurious reads to a receiver, and vice versa */ if (o->proto == FIO_TYPE_TCP) return 0; if ((o->listen && io_u->ddir == DDIR_WRITE) || (!o->listen && io_u->ddir == DDIR_READ)) { td_verror(td, EINVAL, "bad direction"); return 1; } return 0; } #ifdef CONFIG_LINUX_SPLICE static int splice_io_u(int fdin, int fdout, unsigned int len) { int bytes = 0; while (len) { int ret = splice(fdin, NULL, fdout, NULL, len, 0); if (ret < 0) { if (!bytes) bytes = ret; break; } else if (!ret) break; bytes += ret; len -= ret; } return bytes; } /* * Receive bytes from a socket and fill them into the internal pipe */ static int splice_in(struct thread_data *td, struct io_u *io_u) { struct netio_data *nd = td->io_ops->data; return splice_io_u(io_u->file->fd, nd->pipes[1], io_u->xfer_buflen); } /* * Transmit 'len' bytes from the internal pipe */ static int splice_out(struct thread_data *td, struct io_u *io_u, unsigned int len) { struct netio_data *nd = td->io_ops->data; return splice_io_u(nd->pipes[0], io_u->file->fd, len); } static int vmsplice_io_u(struct io_u *io_u, int fd, unsigned int len) { struct iovec iov = { .iov_base = io_u->xfer_buf, .iov_len = len, }; int bytes = 0; while (iov.iov_len) { int ret = vmsplice(fd, &iov, 1, SPLICE_F_MOVE); if (ret < 0) { if (!bytes) bytes = ret; break; } else if (!ret) break; iov.iov_len -= ret; iov.iov_base += ret; bytes += ret; } return bytes; } /* * vmsplice() pipe to io_u buffer */ static int vmsplice_io_u_out(struct thread_data *td, struct io_u *io_u, unsigned int len) { struct netio_data *nd = td->io_ops->data; return vmsplice_io_u(io_u, nd->pipes[0], len); } /* * vmsplice() io_u to pipe */ static int vmsplice_io_u_in(struct thread_data *td, struct io_u *io_u) { struct netio_data *nd = td->io_ops->data; return vmsplice_io_u(io_u, nd->pipes[1], io_u->xfer_buflen); } /* * splice receive - transfer socket data into a pipe using splice, then map * that pipe data into the io_u using vmsplice. */ static int fio_netio_splice_in(struct thread_data *td, struct io_u *io_u) { int ret; ret = splice_in(td, io_u); if (ret > 0) return vmsplice_io_u_out(td, io_u, ret); return ret; } /* * splice transmit - map data from the io_u into a pipe by using vmsplice, * then transfer that pipe to a socket using splice. */ static int fio_netio_splice_out(struct thread_data *td, struct io_u *io_u) { int ret; ret = vmsplice_io_u_in(td, io_u); if (ret > 0) return splice_out(td, io_u, ret); return ret; } #else static int fio_netio_splice_in(struct thread_data *td, struct io_u *io_u) { errno = EOPNOTSUPP; return -1; } static int fio_netio_splice_out(struct thread_data *td, struct io_u *io_u) { errno = EOPNOTSUPP; return -1; } #endif static int fio_netio_send(struct thread_data *td, struct io_u *io_u) { struct netio_data *nd = td->io_ops->data; struct netio_options *o = td->eo; int ret, flags = 0; do { if (o->proto == FIO_TYPE_UDP) { struct sockaddr *to = (struct sockaddr *) &nd->addr; ret = sendto(io_u->file->fd, io_u->xfer_buf, io_u->xfer_buflen, flags, to, sizeof(*to)); } else { /* * if we are going to write more, set MSG_MORE */ #ifdef MSG_MORE if ((td->this_io_bytes[DDIR_WRITE] + io_u->xfer_buflen < td->o.size) && !o->pingpong) flags |= MSG_MORE; #endif ret = send(io_u->file->fd, io_u->xfer_buf, io_u->xfer_buflen, flags); } if (ret > 0) break; ret = poll_wait(td, io_u->file->fd, POLLOUT); if (ret <= 0) break; } while (1); return ret; } static int is_udp_close(struct io_u *io_u, int len) { struct udp_close_msg *msg; if (len != sizeof(struct udp_close_msg)) return 0; msg = io_u->xfer_buf; if (ntohl(msg->magic) != FIO_LINK_OPEN_CLOSE_MAGIC) return 0; if (ntohl(msg->cmd) != FIO_LINK_CLOSE) return 0; return 1; } static int fio_netio_recv(struct thread_data *td, struct io_u *io_u) { struct netio_data *nd = td->io_ops->data; struct netio_options *o = td->eo; int ret, flags = 0; do { if (o->proto == FIO_TYPE_UDP) { socklen_t l; socklen_t *len = &l; struct sockaddr *from; if (o->listen) { from = (struct sockaddr *) &nd->addr; *len = sizeof(nd->addr); } else { from = NULL; len = NULL; } ret = recvfrom(io_u->file->fd, io_u->xfer_buf, io_u->xfer_buflen, flags, from, len); if (is_udp_close(io_u, ret)) { td->done = 1; return 0; } } else { ret = recv(io_u->file->fd, io_u->xfer_buf, io_u->xfer_buflen, flags); } if (ret > 0) break; else if (!ret && (flags & MSG_WAITALL)) break; ret = poll_wait(td, io_u->file->fd, POLLIN); if (ret <= 0) break; flags |= MSG_WAITALL; } while (1); return ret; } static int __fio_netio_queue(struct thread_data *td, struct io_u *io_u, enum fio_ddir ddir) { struct netio_data *nd = td->io_ops->data; struct netio_options *o = td->eo; int ret; if (ddir == DDIR_WRITE) { if (!nd->use_splice || o->proto == FIO_TYPE_UDP || o->proto == FIO_TYPE_UNIX) ret = fio_netio_send(td, io_u); else ret = fio_netio_splice_out(td, io_u); } else if (ddir == DDIR_READ) { if (!nd->use_splice || o->proto == FIO_TYPE_UDP || o->proto == FIO_TYPE_UNIX) ret = fio_netio_recv(td, io_u); else ret = fio_netio_splice_in(td, io_u); } else ret = 0; /* must be a SYNC */ if (ret != (int) io_u->xfer_buflen) { if (ret >= 0) { io_u->resid = io_u->xfer_buflen - ret; io_u->error = 0; return FIO_Q_COMPLETED; } else { int err = errno; if (ddir == DDIR_WRITE && err == EMSGSIZE) return FIO_Q_BUSY; io_u->error = err; } } if (io_u->error) td_verror(td, io_u->error, "xfer"); return FIO_Q_COMPLETED; } static int fio_netio_queue(struct thread_data *td, struct io_u *io_u) { struct netio_options *o = td->eo; int ret; fio_ro_check(td, io_u); ret = __fio_netio_queue(td, io_u, io_u->ddir); if (!o->pingpong || ret != FIO_Q_COMPLETED) return ret; /* * For ping-pong mode, receive or send reply as needed */ if (td_read(td) && io_u->ddir == DDIR_READ) ret = __fio_netio_queue(td, io_u, DDIR_WRITE); else if (td_write(td) && io_u->ddir == DDIR_WRITE) ret = __fio_netio_queue(td, io_u, DDIR_READ); return ret; } static int fio_netio_connect(struct thread_data *td, struct fio_file *f) { struct netio_data *nd = td->io_ops->data; struct netio_options *o = td->eo; int type, domain; if (o->proto == FIO_TYPE_TCP) { domain = AF_INET; type = SOCK_STREAM; } else if (o->proto == FIO_TYPE_UDP) { domain = AF_INET; type = SOCK_DGRAM; } else if (o->proto == FIO_TYPE_UNIX) { domain = AF_UNIX; type = SOCK_STREAM; } else { log_err("fio: bad network type %d\n", o->proto); f->fd = -1; return 1; } f->fd = socket(domain, type, 0); if (f->fd < 0) { td_verror(td, errno, "socket"); return 1; } #ifdef CONFIG_TCP_NODELAY if (o->nodelay && o->proto == FIO_TYPE_TCP) { int optval = 1; if (setsockopt(f->fd, IPPROTO_TCP, TCP_NODELAY, (void *) &optval, sizeof(int)) < 0) { log_err("fio: cannot set TCP_NODELAY option on socket (%s), disable with 'nodelay=0'\n", strerror(errno)); return 1; } } #endif if (o->proto == FIO_TYPE_UDP) { if (!fio_netio_is_multicast(td->o.filename)) return 0; if (o->interface) { struct in_addr interface_addr; if (inet_aton(o->interface, &interface_addr) == 0) { log_err("fio: interface not valid interface IP\n"); close(f->fd); return 1; } if (setsockopt(f->fd, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr, sizeof(interface_addr)) < 0) { td_verror(td, errno, "setsockopt IP_MULTICAST_IF"); close(f->fd); return 1; } } if (setsockopt(f->fd, IPPROTO_IP, IP_MULTICAST_TTL, &o->ttl, sizeof(o->ttl)) < 0) { td_verror(td, errno, "setsockopt IP_MULTICAST_TTL"); close(f->fd); return 1; } return 0; } else if (o->proto == FIO_TYPE_TCP) { socklen_t len = sizeof(nd->addr); if (connect(f->fd, (struct sockaddr *) &nd->addr, len) < 0) { td_verror(td, errno, "connect"); close(f->fd); return 1; } } else { struct sockaddr_un *addr = &nd->addr_un; socklen_t len; len = sizeof(addr->sun_family) + strlen(addr->sun_path) + 1; if (connect(f->fd, (struct sockaddr *) addr, len) < 0) { td_verror(td, errno, "connect"); close(f->fd); return 1; } } return 0; } static int fio_netio_accept(struct thread_data *td, struct fio_file *f) { struct netio_data *nd = td->io_ops->data; struct netio_options *o = td->eo; socklen_t socklen = sizeof(nd->addr); int state; if (o->proto == FIO_TYPE_UDP) { f->fd = nd->listenfd; return 0; } state = td->runstate; td_set_runstate(td, TD_SETTING_UP); log_info("fio: waiting for connection\n"); if (poll_wait(td, nd->listenfd, POLLIN) < 0) goto err; f->fd = accept(nd->listenfd, (struct sockaddr *) &nd->addr, &socklen); if (f->fd < 0) { td_verror(td, errno, "accept"); goto err; } #ifdef CONFIG_TCP_NODELAY if (o->nodelay && o->proto == FIO_TYPE_TCP) { int optval = 1; if (setsockopt(f->fd, IPPROTO_TCP, TCP_NODELAY, (void *) &optval, sizeof(int)) < 0) { log_err("fio: cannot set TCP_NODELAY option on socket (%s), disable with 'nodelay=0'\n", strerror(errno)); return 1; } } #endif reset_all_stats(td); td_set_runstate(td, state); return 0; err: td_set_runstate(td, state); return 1; } static void fio_netio_udp_close(struct thread_data *td, struct fio_file *f) { struct netio_data *nd = td->io_ops->data; struct udp_close_msg msg; struct sockaddr *to = (struct sockaddr *) &nd->addr; int ret; msg.magic = htonl(FIO_LINK_OPEN_CLOSE_MAGIC); msg.cmd = htonl(FIO_LINK_CLOSE); ret = sendto(f->fd, (void *) &msg, sizeof(msg), MSG_WAITALL, to, sizeof(nd->addr)); if (ret < 0) td_verror(td, errno, "sendto udp link close"); } static int fio_netio_close_file(struct thread_data *td, struct fio_file *f) { struct netio_options *o = td->eo; /* * If this is an UDP connection, notify the receiver that we are * closing down the link */ if (o->proto == FIO_TYPE_UDP) fio_netio_udp_close(td, f); return generic_close_file(td, f); } static int fio_netio_udp_recv_open(struct thread_data *td, struct fio_file *f) { struct netio_data *nd = td->io_ops->data; struct udp_close_msg msg; struct sockaddr *to = (struct sockaddr *) &nd->addr; socklen_t len = sizeof(nd->addr); int ret; ret = recvfrom(f->fd, (void *) &msg, sizeof(msg), MSG_WAITALL, to, &len); if (ret < 0) { td_verror(td, errno, "recvfrom udp link open"); return ret; } if (ntohl(msg.magic) != FIO_LINK_OPEN_CLOSE_MAGIC || ntohl(msg.cmd) != FIO_LINK_OPEN) { log_err("fio: bad udp open magic %x/%x\n", ntohl(msg.magic), ntohl(msg.cmd)); return -1; } return 0; } static int fio_netio_udp_send_open(struct thread_data *td, struct fio_file *f) { struct netio_data *nd = td->io_ops->data; struct udp_close_msg msg; struct sockaddr *to = (struct sockaddr *) &nd->addr; int ret; msg.magic = htonl(FIO_LINK_OPEN_CLOSE_MAGIC); msg.cmd = htonl(FIO_LINK_OPEN); ret = sendto(f->fd, (void *) &msg, sizeof(msg), MSG_WAITALL, to, sizeof(nd->addr)); if (ret < 0) { td_verror(td, errno, "sendto udp link open"); return ret; } return 0; } static int fio_netio_open_file(struct thread_data *td, struct fio_file *f) { int ret; struct netio_options *o = td->eo; if (o->listen) ret = fio_netio_accept(td, f); else ret = fio_netio_connect(td, f); if (ret) { f->fd = -1; return ret; } if (o->proto == FIO_TYPE_UDP) { if (td_write(td)) ret = fio_netio_udp_send_open(td, f); else { int state; state = td->runstate; td_set_runstate(td, TD_SETTING_UP); ret = fio_netio_udp_recv_open(td, f); td_set_runstate(td, state); } } if (ret) fio_netio_close_file(td, f); return ret; } static int fio_netio_setup_connect_inet(struct thread_data *td, const char *host, unsigned short port) { struct netio_data *nd = td->io_ops->data; if (!host) { log_err("fio: connect with no host to connect to.\n"); if (td_read(td)) log_err("fio: did you forget to set 'listen'?\n"); td_verror(td, EINVAL, "no hostname= set"); return 1; } nd->addr.sin_family = AF_INET; nd->addr.sin_port = htons(port); if (inet_aton(host, &nd->addr.sin_addr) != 1) { struct hostent *hent; hent = gethostbyname(host); if (!hent) { td_verror(td, errno, "gethostbyname"); return 1; } memcpy(&nd->addr.sin_addr, hent->h_addr, 4); } return 0; } static int fio_netio_setup_connect_unix(struct thread_data *td, const char *path) { struct netio_data *nd = td->io_ops->data; struct sockaddr_un *soun = &nd->addr_un; soun->sun_family = AF_UNIX; strcpy(soun->sun_path, path); return 0; } static int fio_netio_setup_connect(struct thread_data *td) { struct netio_options *o = td->eo; if (o->proto == FIO_TYPE_UDP || o->proto == FIO_TYPE_TCP) return fio_netio_setup_connect_inet(td, td->o.filename,o->port); else return fio_netio_setup_connect_unix(td, td->o.filename); } static int fio_netio_setup_listen_unix(struct thread_data *td, const char *path) { struct netio_data *nd = td->io_ops->data; struct sockaddr_un *addr = &nd->addr_un; mode_t mode; int len, fd; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { log_err("fio: socket: %s\n", strerror(errno)); return -1; } mode = umask(000); memset(addr, 0, sizeof(*addr)); addr->sun_family = AF_UNIX; strcpy(addr->sun_path, path); unlink(path); len = sizeof(addr->sun_family) + strlen(path) + 1; if (bind(fd, (struct sockaddr *) addr, len) < 0) { log_err("fio: bind: %s\n", strerror(errno)); close(fd); return -1; } umask(mode); nd->listenfd = fd; return 0; } static int fio_netio_setup_listen_inet(struct thread_data *td, short port) { struct netio_data *nd = td->io_ops->data; struct netio_options *o = td->eo; struct ip_mreq mr; struct sockaddr_in sin; int fd, opt, type; memset(&sin, 0, sizeof(sin)); if (o->proto == FIO_TYPE_TCP) type = SOCK_STREAM; else type = SOCK_DGRAM; fd = socket(AF_INET, type, 0); if (fd < 0) { td_verror(td, errno, "socket"); return 1; } opt = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &opt, sizeof(opt)) < 0) { td_verror(td, errno, "setsockopt"); close(fd); return 1; } #ifdef SO_REUSEPORT if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (void *) &opt, sizeof(opt)) < 0) { td_verror(td, errno, "setsockopt"); close(fd); return 1; } #endif if (td->o.filename){ if(o->proto != FIO_TYPE_UDP || !fio_netio_is_multicast(td->o.filename)) { log_err("fio: hostname not valid for non-multicast inbound network IO\n"); close(fd); return 1; } inet_aton(td->o.filename, &sin.sin_addr); mr.imr_multiaddr = sin.sin_addr; if (o->interface) { if (inet_aton(o->interface, &mr.imr_interface) == 0) { log_err("fio: interface not valid interface IP\n"); close(fd); return 1; } } else { mr.imr_interface.s_addr = htonl(INADDR_ANY); } if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr, sizeof(mr)) < 0) { td_verror(td, errno, "setsockopt IP_ADD_MEMBERSHIP"); close(fd); return 1; } } nd->addr.sin_family = AF_INET; nd->addr.sin_addr.s_addr = sin.sin_addr.s_addr ? sin.sin_addr.s_addr : htonl(INADDR_ANY); nd->addr.sin_port = htons(port); if (bind(fd, (struct sockaddr *) &nd->addr, sizeof(nd->addr)) < 0) { td_verror(td, errno, "bind"); return 1; } nd->listenfd = fd; return 0; } static int fio_netio_setup_listen(struct thread_data *td) { struct netio_data *nd = td->io_ops->data; struct netio_options *o = td->eo; int ret; if (o->proto == FIO_TYPE_UDP || o->proto == FIO_TYPE_TCP) ret = fio_netio_setup_listen_inet(td, o->port); else ret = fio_netio_setup_listen_unix(td, td->o.filename); if (ret) return ret; if (o->proto == FIO_TYPE_UDP) return 0; if (listen(nd->listenfd, 10) < 0) { td_verror(td, errno, "listen"); nd->listenfd = -1; return 1; } return 0; } static int fio_netio_init(struct thread_data *td) { struct netio_options *o = td->eo; int ret; #ifdef WIN32 WSADATA wsd; WSAStartup(MAKEWORD(2,2), &wsd); #endif if (td_random(td)) { log_err("fio: network IO can't be random\n"); return 1; } if (o->proto == FIO_TYPE_UNIX && o->port) { log_err("fio: network IO port not valid with unix socket\n"); return 1; } else if (o->proto != FIO_TYPE_UNIX && !o->port) { log_err("fio: network IO requires port for tcp or udp\n"); return 1; } if (o->proto != FIO_TYPE_TCP) { if (o->listen) { log_err("fio: listen only valid for TCP proto IO\n"); return 1; } if (td_rw(td)) { log_err("fio: datagram network connections must be" " read OR write\n"); return 1; } if (o->proto == FIO_TYPE_UNIX && !td->o.filename) { log_err("fio: UNIX sockets need host/filename\n"); return 1; } o->listen = td_read(td); } if (o->listen) ret = fio_netio_setup_listen(td); else ret = fio_netio_setup_connect(td); return ret; } static void fio_netio_cleanup(struct thread_data *td) { struct netio_data *nd = td->io_ops->data; if (nd) { if (nd->listenfd != -1) close(nd->listenfd); if (nd->pipes[0] != -1) close(nd->pipes[0]); if (nd->pipes[1] != -1) close(nd->pipes[1]); free(nd); } } static int fio_netio_setup(struct thread_data *td) { struct netio_data *nd; if (!td->files_index) { add_file(td, td->o.filename ?: "net"); td->o.nr_files = td->o.nr_files ?: 1; } if (!td->io_ops->data) { nd = malloc(sizeof(*nd));; memset(nd, 0, sizeof(*nd)); nd->listenfd = -1; nd->pipes[0] = nd->pipes[1] = -1; td->io_ops->data = nd; } return 0; } static void fio_netio_terminate(struct thread_data *td) { kill(td->pid, SIGUSR2); } #ifdef CONFIG_LINUX_SPLICE static int fio_netio_setup_splice(struct thread_data *td) { struct netio_data *nd; fio_netio_setup(td); nd = td->io_ops->data; if (nd) { if (pipe(nd->pipes) < 0) return 1; nd->use_splice = 1; return 0; } return 1; } static struct ioengine_ops ioengine_splice = { .name = "netsplice", .version = FIO_IOOPS_VERSION, .prep = fio_netio_prep, .queue = fio_netio_queue, .setup = fio_netio_setup_splice, .init = fio_netio_init, .cleanup = fio_netio_cleanup, .open_file = fio_netio_open_file, .close_file = fio_netio_close_file, .terminate = fio_netio_terminate, .options = options, .option_struct_size = sizeof(struct netio_options), .flags = FIO_SYNCIO | FIO_DISKLESSIO | FIO_UNIDIR | FIO_PIPEIO, }; #endif static struct ioengine_ops ioengine_rw = { .name = "net", .version = FIO_IOOPS_VERSION, .prep = fio_netio_prep, .queue = fio_netio_queue, .setup = fio_netio_setup, .init = fio_netio_init, .cleanup = fio_netio_cleanup, .open_file = fio_netio_open_file, .close_file = fio_netio_close_file, .terminate = fio_netio_terminate, .options = options, .option_struct_size = sizeof(struct netio_options), .flags = FIO_SYNCIO | FIO_DISKLESSIO | FIO_UNIDIR | FIO_PIPEIO | FIO_BIT_BASED, }; static int str_hostname_cb(void *data, const char *input) { struct netio_options *o = data; if (o->td->o.filename) free(o->td->o.filename); o->td->o.filename = strdup(input); return 0; } static void fio_init fio_netio_register(void) { register_ioengine(&ioengine_rw); #ifdef CONFIG_LINUX_SPLICE register_ioengine(&ioengine_splice); #endif } static void fio_exit fio_netio_unregister(void) { unregister_ioengine(&ioengine_rw); #ifdef CONFIG_LINUX_SPLICE unregister_ioengine(&ioengine_splice); #endif } fio-2.1.3/engines/null.c000066400000000000000000000045511222032232000150310ustar00rootroot00000000000000/* * null engine * * IO engine that doesn't do any real IO transfers, it just pretends to. * The main purpose is to test fio itself. * */ #include #include #include #include #include #include "../fio.h" struct null_data { struct io_u **io_us; int queued; int events; }; static struct io_u *fio_null_event(struct thread_data *td, int event) { struct null_data *nd = td->io_ops->data; return nd->io_us[event]; } static int fio_null_getevents(struct thread_data *td, unsigned int min_events, unsigned int fio_unused max, struct timespec fio_unused *t) { struct null_data *nd = td->io_ops->data; int ret = 0; if (min_events) { ret = nd->events; nd->events = 0; } return ret; } static int fio_null_commit(struct thread_data *td) { struct null_data *nd = td->io_ops->data; if (!nd->events) { io_u_mark_submit(td, nd->queued); nd->events = nd->queued; nd->queued = 0; } return 0; } static int fio_null_queue(struct thread_data *td, struct io_u *io_u) { struct null_data *nd = td->io_ops->data; fio_ro_check(td, io_u); if (td->io_ops->flags & FIO_SYNCIO) return FIO_Q_COMPLETED; if (nd->events) return FIO_Q_BUSY; nd->io_us[nd->queued++] = io_u; return FIO_Q_QUEUED; } static int fio_null_open(struct thread_data fio_unused *td, struct fio_file fio_unused *f) { return 0; } static void fio_null_cleanup(struct thread_data *td) { struct null_data *nd = td->io_ops->data; if (nd) { if (nd->io_us) free(nd->io_us); free(nd); } } static int fio_null_init(struct thread_data *td) { struct null_data *nd = malloc(sizeof(*nd)); memset(nd, 0, sizeof(*nd)); if (td->o.iodepth != 1) { nd->io_us = malloc(td->o.iodepth * sizeof(struct io_u *)); memset(nd->io_us, 0, td->o.iodepth * sizeof(struct io_u *)); } else td->io_ops->flags |= FIO_SYNCIO; td->io_ops->data = nd; return 0; } static struct ioengine_ops ioengine = { .name = "null", .version = FIO_IOOPS_VERSION, .queue = fio_null_queue, .commit = fio_null_commit, .getevents = fio_null_getevents, .event = fio_null_event, .init = fio_null_init, .cleanup = fio_null_cleanup, .open_file = fio_null_open, .flags = FIO_DISKLESSIO, }; static void fio_init fio_null_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_null_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/posixaio.c000066400000000000000000000124131222032232000157060ustar00rootroot00000000000000/* * posixaio engine * * IO engine that uses the posix defined aio interface. * */ #include #include #include #include #include #include "../fio.h" struct posixaio_data { struct io_u **aio_events; unsigned int queued; }; static int fill_timespec(struct timespec *ts) { #ifdef CONFIG_CLOCK_GETTIME #ifdef CONFIG_CLOCK_MONOTONIC clockid_t clk = CLOCK_MONOTONIC; #else clockid_t clk = CLOCK_REALTIME; #endif if (!clock_gettime(clk, ts)) return 0; perror("clock_gettime"); return 1; #else struct timeval tv; gettimeofday(&tv, NULL); ts->tv_sec = tv.tv_sec; ts->tv_nsec = tv.tv_usec * 1000; return 0; #endif } static unsigned long long ts_utime_since_now(struct timespec *t) { long long sec, nsec; struct timespec now; if (fill_timespec(&now)) return 0; sec = now.tv_sec - t->tv_sec; nsec = now.tv_nsec - t->tv_nsec; if (sec > 0 && nsec < 0) { sec--; nsec += 1000000000; } sec *= 1000000; nsec /= 1000; return sec + nsec; } static int fio_posixaio_cancel(struct thread_data fio_unused *td, struct io_u *io_u) { struct fio_file *f = io_u->file; int r = aio_cancel(f->fd, &io_u->aiocb); if (r == AIO_ALLDONE || r == AIO_CANCELED) return 0; return 1; } static int fio_posixaio_prep(struct thread_data fio_unused *td, struct io_u *io_u) { os_aiocb_t *aiocb = &io_u->aiocb; struct fio_file *f = io_u->file; aiocb->aio_fildes = f->fd; aiocb->aio_buf = io_u->xfer_buf; aiocb->aio_nbytes = io_u->xfer_buflen; aiocb->aio_offset = io_u->offset; aiocb->aio_sigevent.sigev_notify = SIGEV_NONE; io_u->seen = 0; return 0; } #define SUSPEND_ENTRIES 8 static int fio_posixaio_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec *t) { struct posixaio_data *pd = td->io_ops->data; os_aiocb_t *suspend_list[SUSPEND_ENTRIES]; struct timespec start; int have_timeout = 0; int suspend_entries; struct io_u *io_u; unsigned int r; int i; if (t && !fill_timespec(&start)) have_timeout = 1; else memset(&start, 0, sizeof(start)); r = 0; restart: memset(suspend_list, 0, sizeof(*suspend_list)); suspend_entries = 0; io_u_qiter(&td->io_u_all, io_u, i) { int err; if (io_u->seen || !(io_u->flags & IO_U_F_FLIGHT)) continue; err = aio_error(&io_u->aiocb); if (err == EINPROGRESS) { if (suspend_entries < SUSPEND_ENTRIES) { suspend_list[suspend_entries] = &io_u->aiocb; suspend_entries++; } continue; } io_u->seen = 1; pd->queued--; pd->aio_events[r++] = io_u; if (err == ECANCELED) io_u->resid = io_u->xfer_buflen; else if (!err) { ssize_t retval = aio_return(&io_u->aiocb); io_u->resid = io_u->xfer_buflen - retval; } else io_u->error = err; } if (r >= min) return r; if (have_timeout) { unsigned long long usec; usec = (t->tv_sec * 1000000) + (t->tv_nsec / 1000); if (ts_utime_since_now(&start) > usec) return r; } /* * must have some in-flight, wait for at least one */ aio_suspend((const os_aiocb_t * const *)suspend_list, suspend_entries, t); goto restart; } static struct io_u *fio_posixaio_event(struct thread_data *td, int event) { struct posixaio_data *pd = td->io_ops->data; return pd->aio_events[event]; } static int fio_posixaio_queue(struct thread_data *td, struct io_u *io_u) { struct posixaio_data *pd = td->io_ops->data; os_aiocb_t *aiocb = &io_u->aiocb; int ret; fio_ro_check(td, io_u); if (io_u->ddir == DDIR_READ) ret = aio_read(aiocb); else if (io_u->ddir == DDIR_WRITE) ret = aio_write(aiocb); else if (io_u->ddir == DDIR_TRIM) { if (pd->queued) return FIO_Q_BUSY; do_io_u_trim(td, io_u); return FIO_Q_COMPLETED; } else { #ifdef CONFIG_POSIXAIO_FSYNC ret = aio_fsync(O_SYNC, aiocb); #else if (pd->queued) return FIO_Q_BUSY; do_io_u_sync(td, io_u); return FIO_Q_COMPLETED; #endif } if (ret) { /* * At least OSX has a very low limit on the number of pending * IOs, so if it returns EAGAIN, we are out of resources * to queue more. Just return FIO_Q_BUSY to naturally * drop off at this depth. */ if (errno == EAGAIN) return FIO_Q_BUSY; io_u->error = errno; td_verror(td, io_u->error, "xfer"); return FIO_Q_COMPLETED; } pd->queued++; return FIO_Q_QUEUED; } static void fio_posixaio_cleanup(struct thread_data *td) { struct posixaio_data *pd = td->io_ops->data; if (pd) { free(pd->aio_events); free(pd); } } static int fio_posixaio_init(struct thread_data *td) { struct posixaio_data *pd = malloc(sizeof(*pd)); memset(pd, 0, sizeof(*pd)); pd->aio_events = malloc(td->o.iodepth * sizeof(struct io_u *)); memset(pd->aio_events, 0, td->o.iodepth * sizeof(struct io_u *)); td->io_ops->data = pd; return 0; } static struct ioengine_ops ioengine = { .name = "posixaio", .version = FIO_IOOPS_VERSION, .init = fio_posixaio_init, .prep = fio_posixaio_prep, .queue = fio_posixaio_queue, .cancel = fio_posixaio_cancel, .getevents = fio_posixaio_getevents, .event = fio_posixaio_event, .cleanup = fio_posixaio_cleanup, .open_file = generic_open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, }; static void fio_init fio_posixaio_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_posixaio_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/rdma.c000066400000000000000000000712511222032232000150030ustar00rootroot00000000000000/* * RDMA I/O engine * * RDMA I/O engine based on the IB verbs and RDMA/CM user space libraries. * Supports both RDMA memory semantics and channel semantics * for the InfiniBand, RoCE and iWARP protocols. * * You will need the Linux RDMA software installed, either * from your Linux distributor or directly from openfabrics.org: * * http://www.openfabrics.org/downloads/OFED/ * * Exchanging steps of RDMA ioengine control messages: * 1. client side sends test mode (RDMA_WRITE/RDMA_READ/SEND) * to server side. * 2. server side parses test mode, and sends back confirmation * to client side. In RDMA WRITE/READ test, this confirmation * includes memory information, such as rkey, address. * 3. client side initiates test loop. * 4. In RDMA WRITE/READ test, client side sends a completion * notification to server side. Server side updates its * td->done as true. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../fio.h" #include "../hash.h" #include #include #define FIO_RDMA_MAX_IO_DEPTH 512 enum rdma_io_mode { FIO_RDMA_UNKNOWN = 0, FIO_RDMA_MEM_WRITE, FIO_RDMA_MEM_READ, FIO_RDMA_CHA_SEND, FIO_RDMA_CHA_RECV }; struct remote_u { uint64_t buf; uint32_t rkey; uint32_t size; }; struct rdma_info_blk { uint32_t mode; /* channel semantic or memory semantic */ uint32_t nr; /* client: io depth server: number of records for memory semantic */ struct remote_u rmt_us[FIO_RDMA_MAX_IO_DEPTH]; }; struct rdma_io_u_data { uint64_t wr_id; struct ibv_send_wr sq_wr; struct ibv_recv_wr rq_wr; struct ibv_sge rdma_sgl; }; struct rdmaio_data { int is_client; enum rdma_io_mode rdma_protocol; char host[64]; struct sockaddr_in addr; struct ibv_recv_wr rq_wr; struct ibv_sge recv_sgl; struct rdma_info_blk recv_buf; struct ibv_mr *recv_mr; struct ibv_send_wr sq_wr; struct ibv_sge send_sgl; struct rdma_info_blk send_buf; struct ibv_mr *send_mr; struct ibv_comp_channel *channel; struct ibv_cq *cq; struct ibv_pd *pd; struct ibv_qp *qp; pthread_t cmthread; struct rdma_event_channel *cm_channel; struct rdma_cm_id *cm_id; struct rdma_cm_id *child_cm_id; int cq_event_num; struct remote_u *rmt_us; int rmt_nr; struct io_u **io_us_queued; int io_u_queued_nr; struct io_u **io_us_flight; int io_u_flight_nr; struct io_u **io_us_completed; int io_u_completed_nr; struct frand_state rand_state; }; static int client_recv(struct thread_data *td, struct ibv_wc *wc) { struct rdmaio_data *rd = td->io_ops->data; if (wc->byte_len != sizeof(rd->recv_buf)) { log_err("Received bogus data, size %d\n", wc->byte_len); return 1; } /* store mr info for MEMORY semantic */ if ((rd->rdma_protocol == FIO_RDMA_MEM_WRITE) || (rd->rdma_protocol == FIO_RDMA_MEM_READ)) { /* struct flist_head *entry; */ int i = 0; rd->rmt_nr = ntohl(rd->recv_buf.nr); for (i = 0; i < rd->rmt_nr; i++) { rd->rmt_us[i].buf = ntohll(rd->recv_buf.rmt_us[i].buf); rd->rmt_us[i].rkey = ntohl(rd->recv_buf.rmt_us[i].rkey); rd->rmt_us[i].size = ntohl(rd->recv_buf.rmt_us[i].size); dprint(FD_IO, "fio: Received rkey %x addr %" PRIx64 " len %d from peer\n", rd->rmt_us[i].rkey, rd->rmt_us[i].buf, rd->rmt_us[i].size); } } return 0; } static int server_recv(struct thread_data *td, struct ibv_wc *wc) { struct rdmaio_data *rd = td->io_ops->data; if (wc->wr_id == FIO_RDMA_MAX_IO_DEPTH) { rd->rdma_protocol = ntohl(rd->recv_buf.mode); /* CHANNEL semantic, do nothing */ if (rd->rdma_protocol == FIO_RDMA_CHA_SEND) rd->rdma_protocol = FIO_RDMA_CHA_RECV; } return 0; } static int cq_event_handler(struct thread_data *td, enum ibv_wc_opcode opcode) { struct rdmaio_data *rd = td->io_ops->data; struct ibv_wc wc; struct rdma_io_u_data *r_io_u_d; int ret; int compevnum = 0; int i; while ((ret = ibv_poll_cq(rd->cq, 1, &wc)) == 1) { ret = 0; compevnum++; if (wc.status) { log_err("fio: cq completion status %d(%s)\n", wc.status, ibv_wc_status_str(wc.status)); return -1; } switch (wc.opcode) { case IBV_WC_RECV: if (rd->is_client == 1) client_recv(td, &wc); else server_recv(td, &wc); if (wc.wr_id == FIO_RDMA_MAX_IO_DEPTH) break; for (i = 0; i < rd->io_u_flight_nr; i++) { r_io_u_d = rd->io_us_flight[i]->engine_data; if (wc.wr_id == r_io_u_d->rq_wr.wr_id) { rd->io_us_flight[i]->resid = rd->io_us_flight[i]->buflen - wc.byte_len; rd->io_us_flight[i]->error = 0; rd->io_us_completed[rd-> io_u_completed_nr] = rd->io_us_flight[i]; rd->io_u_completed_nr++; break; } } if (i == rd->io_u_flight_nr) log_err("fio: recv wr %" PRId64 " not found\n", wc.wr_id); else { /* put the last one into middle of the list */ rd->io_us_flight[i] = rd->io_us_flight[rd->io_u_flight_nr - 1]; rd->io_u_flight_nr--; } break; case IBV_WC_SEND: case IBV_WC_RDMA_WRITE: case IBV_WC_RDMA_READ: if (wc.wr_id == FIO_RDMA_MAX_IO_DEPTH) break; for (i = 0; i < rd->io_u_flight_nr; i++) { r_io_u_d = rd->io_us_flight[i]->engine_data; if (wc.wr_id == r_io_u_d->sq_wr.wr_id) { rd->io_us_completed[rd-> io_u_completed_nr] = rd->io_us_flight[i]; rd->io_u_completed_nr++; break; } } if (i == rd->io_u_flight_nr) log_err("fio: send wr %" PRId64 " not found\n", wc.wr_id); else { /* put the last one into middle of the list */ rd->io_us_flight[i] = rd->io_us_flight[rd->io_u_flight_nr - 1]; rd->io_u_flight_nr--; } break; default: log_info("fio: unknown completion event %d\n", wc.opcode); return -1; } rd->cq_event_num++; } if (ret) { log_err("fio: poll error %d\n", ret); return 1; } return compevnum; } /* * Return -1 for error and 'nr events' for a positive number * of events */ static int rdma_poll_wait(struct thread_data *td, enum ibv_wc_opcode opcode) { struct rdmaio_data *rd = td->io_ops->data; struct ibv_cq *ev_cq; void *ev_ctx; int ret; if (rd->cq_event_num > 0) { /* previous left */ rd->cq_event_num--; return 0; } again: if (ibv_get_cq_event(rd->channel, &ev_cq, &ev_ctx) != 0) { log_err("fio: Failed to get cq event!\n"); return -1; } if (ev_cq != rd->cq) { log_err("fio: Unknown CQ!\n"); return -1; } if (ibv_req_notify_cq(rd->cq, 0) != 0) { log_err("fio: Failed to set notify!\n"); return -1; } ret = cq_event_handler(td, opcode); if (ret < 1) goto again; ibv_ack_cq_events(rd->cq, ret); rd->cq_event_num--; return ret; } static int fio_rdmaio_setup_qp(struct thread_data *td) { struct rdmaio_data *rd = td->io_ops->data; struct ibv_qp_init_attr init_attr; int qp_depth = td->o.iodepth * 2; /* 2 times of io depth */ if (rd->is_client == 0) rd->pd = ibv_alloc_pd(rd->child_cm_id->verbs); else rd->pd = ibv_alloc_pd(rd->cm_id->verbs); if (rd->pd == NULL) { log_err("fio: ibv_alloc_pd fail\n"); return 1; } if (rd->is_client == 0) rd->channel = ibv_create_comp_channel(rd->child_cm_id->verbs); else rd->channel = ibv_create_comp_channel(rd->cm_id->verbs); if (rd->channel == NULL) { log_err("fio: ibv_create_comp_channel fail\n"); goto err1; } if (qp_depth < 16) qp_depth = 16; if (rd->is_client == 0) rd->cq = ibv_create_cq(rd->child_cm_id->verbs, qp_depth, rd, rd->channel, 0); else rd->cq = ibv_create_cq(rd->cm_id->verbs, qp_depth, rd, rd->channel, 0); if (rd->cq == NULL) { log_err("fio: ibv_create_cq failed\n"); goto err2; } if (ibv_req_notify_cq(rd->cq, 0) != 0) { log_err("fio: ibv_create_cq failed\n"); goto err3; } /* create queue pair */ memset(&init_attr, 0, sizeof(init_attr)); init_attr.cap.max_send_wr = qp_depth; init_attr.cap.max_recv_wr = qp_depth; init_attr.cap.max_recv_sge = 1; init_attr.cap.max_send_sge = 1; init_attr.qp_type = IBV_QPT_RC; init_attr.send_cq = rd->cq; init_attr.recv_cq = rd->cq; if (rd->is_client == 0) { if (rdma_create_qp(rd->child_cm_id, rd->pd, &init_attr) != 0) { log_err("fio: rdma_create_qp failed\n"); goto err3; } rd->qp = rd->child_cm_id->qp; } else { if (rdma_create_qp(rd->cm_id, rd->pd, &init_attr) != 0) { log_err("fio: rdma_create_qp failed\n"); goto err3; } rd->qp = rd->cm_id->qp; } return 0; err3: ibv_destroy_cq(rd->cq); err2: ibv_destroy_comp_channel(rd->channel); err1: ibv_dealloc_pd(rd->pd); return 1; } static int fio_rdmaio_setup_control_msg_buffers(struct thread_data *td) { struct rdmaio_data *rd = td->io_ops->data; rd->recv_mr = ibv_reg_mr(rd->pd, &rd->recv_buf, sizeof(rd->recv_buf), IBV_ACCESS_LOCAL_WRITE); if (rd->recv_mr == NULL) { log_err("fio: recv_buf reg_mr failed\n"); return 1; } rd->send_mr = ibv_reg_mr(rd->pd, &rd->send_buf, sizeof(rd->send_buf), 0); if (rd->send_mr == NULL) { log_err("fio: send_buf reg_mr failed\n"); ibv_dereg_mr(rd->recv_mr); return 1; } /* setup work request */ /* recv wq */ rd->recv_sgl.addr = (uint64_t) (unsigned long)&rd->recv_buf; rd->recv_sgl.length = sizeof(rd->recv_buf); rd->recv_sgl.lkey = rd->recv_mr->lkey; rd->rq_wr.sg_list = &rd->recv_sgl; rd->rq_wr.num_sge = 1; rd->rq_wr.wr_id = FIO_RDMA_MAX_IO_DEPTH; /* send wq */ rd->send_sgl.addr = (uint64_t) (unsigned long)&rd->send_buf; rd->send_sgl.length = sizeof(rd->send_buf); rd->send_sgl.lkey = rd->send_mr->lkey; rd->sq_wr.opcode = IBV_WR_SEND; rd->sq_wr.send_flags = IBV_SEND_SIGNALED; rd->sq_wr.sg_list = &rd->send_sgl; rd->sq_wr.num_sge = 1; rd->sq_wr.wr_id = FIO_RDMA_MAX_IO_DEPTH; return 0; } static int get_next_channel_event(struct thread_data *td, struct rdma_event_channel *channel, enum rdma_cm_event_type wait_event) { struct rdmaio_data *rd = td->io_ops->data; struct rdma_cm_event *event; int ret; ret = rdma_get_cm_event(channel, &event); if (ret) { log_err("fio: rdma_get_cm_event: %d\n", ret); return 1; } if (event->event != wait_event) { log_err("fio: event is %s instead of %s\n", rdma_event_str(event->event), rdma_event_str(wait_event)); return 1; } switch (event->event) { case RDMA_CM_EVENT_CONNECT_REQUEST: rd->child_cm_id = event->id; break; default: break; } rdma_ack_cm_event(event); return 0; } static int fio_rdmaio_prep(struct thread_data *td, struct io_u *io_u) { struct rdmaio_data *rd = td->io_ops->data; struct rdma_io_u_data *r_io_u_d; r_io_u_d = io_u->engine_data; switch (rd->rdma_protocol) { case FIO_RDMA_MEM_WRITE: case FIO_RDMA_MEM_READ: r_io_u_d->rdma_sgl.addr = (uint64_t) (unsigned long)io_u->buf; r_io_u_d->rdma_sgl.lkey = io_u->mr->lkey; r_io_u_d->sq_wr.wr_id = r_io_u_d->wr_id; r_io_u_d->sq_wr.send_flags = IBV_SEND_SIGNALED; r_io_u_d->sq_wr.sg_list = &r_io_u_d->rdma_sgl; r_io_u_d->sq_wr.num_sge = 1; break; case FIO_RDMA_CHA_SEND: r_io_u_d->rdma_sgl.addr = (uint64_t) (unsigned long)io_u->buf; r_io_u_d->rdma_sgl.lkey = io_u->mr->lkey; r_io_u_d->rdma_sgl.length = io_u->buflen; r_io_u_d->sq_wr.wr_id = r_io_u_d->wr_id; r_io_u_d->sq_wr.opcode = IBV_WR_SEND; r_io_u_d->sq_wr.send_flags = IBV_SEND_SIGNALED; r_io_u_d->sq_wr.sg_list = &r_io_u_d->rdma_sgl; r_io_u_d->sq_wr.num_sge = 1; break; case FIO_RDMA_CHA_RECV: r_io_u_d->rdma_sgl.addr = (uint64_t) (unsigned long)io_u->buf; r_io_u_d->rdma_sgl.lkey = io_u->mr->lkey; r_io_u_d->rdma_sgl.length = io_u->buflen; r_io_u_d->rq_wr.wr_id = r_io_u_d->wr_id; r_io_u_d->rq_wr.sg_list = &r_io_u_d->rdma_sgl; r_io_u_d->rq_wr.num_sge = 1; break; default: log_err("fio: unknown rdma protocol - %d\n", rd->rdma_protocol); break; } return 0; } static struct io_u *fio_rdmaio_event(struct thread_data *td, int event) { struct rdmaio_data *rd = td->io_ops->data; struct io_u *io_u; int i; io_u = rd->io_us_completed[0]; for (i = 0; i < rd->io_u_completed_nr - 1; i++) rd->io_us_completed[i] = rd->io_us_completed[i + 1]; rd->io_u_completed_nr--; dprint_io_u(io_u, "fio_rdmaio_event"); return io_u; } static int fio_rdmaio_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec *t) { struct rdmaio_data *rd = td->io_ops->data; enum ibv_wc_opcode comp_opcode; struct ibv_cq *ev_cq; void *ev_ctx; int ret, r = 0; comp_opcode = IBV_WC_RDMA_WRITE; switch (rd->rdma_protocol) { case FIO_RDMA_MEM_WRITE: comp_opcode = IBV_WC_RDMA_WRITE; break; case FIO_RDMA_MEM_READ: comp_opcode = IBV_WC_RDMA_READ; break; case FIO_RDMA_CHA_SEND: comp_opcode = IBV_WC_SEND; break; case FIO_RDMA_CHA_RECV: comp_opcode = IBV_WC_RECV; break; default: log_err("fio: unknown rdma protocol - %d\n", rd->rdma_protocol); break; } if (rd->cq_event_num > 0) { /* previous left */ rd->cq_event_num--; return 0; } again: if (ibv_get_cq_event(rd->channel, &ev_cq, &ev_ctx) != 0) { log_err("fio: Failed to get cq event!\n"); return -1; } if (ev_cq != rd->cq) { log_err("fio: Unknown CQ!\n"); return -1; } if (ibv_req_notify_cq(rd->cq, 0) != 0) { log_err("fio: Failed to set notify!\n"); return -1; } ret = cq_event_handler(td, comp_opcode); if (ret < 1) goto again; ibv_ack_cq_events(rd->cq, ret); r += ret; if (r < min) goto again; rd->cq_event_num -= r; return r; } static int fio_rdmaio_send(struct thread_data *td, struct io_u **io_us, unsigned int nr) { struct rdmaio_data *rd = td->io_ops->data; struct ibv_send_wr *bad_wr; #if 0 enum ibv_wc_opcode comp_opcode; comp_opcode = IBV_WC_RDMA_WRITE; #endif int i; long index; struct rdma_io_u_data *r_io_u_d; r_io_u_d = NULL; for (i = 0; i < nr; i++) { /* RDMA_WRITE or RDMA_READ */ switch (rd->rdma_protocol) { case FIO_RDMA_MEM_WRITE: /* compose work request */ r_io_u_d = io_us[i]->engine_data; index = __rand(&rd->rand_state) % rd->rmt_nr; r_io_u_d->sq_wr.opcode = IBV_WR_RDMA_WRITE; r_io_u_d->sq_wr.wr.rdma.rkey = rd->rmt_us[index].rkey; r_io_u_d->sq_wr.wr.rdma.remote_addr = \ rd->rmt_us[index].buf; r_io_u_d->sq_wr.sg_list->length = io_us[i]->buflen; break; case FIO_RDMA_MEM_READ: /* compose work request */ r_io_u_d = io_us[i]->engine_data; index = __rand(&rd->rand_state) % rd->rmt_nr; r_io_u_d->sq_wr.opcode = IBV_WR_RDMA_READ; r_io_u_d->sq_wr.wr.rdma.rkey = rd->rmt_us[index].rkey; r_io_u_d->sq_wr.wr.rdma.remote_addr = \ rd->rmt_us[index].buf; r_io_u_d->sq_wr.sg_list->length = io_us[i]->buflen; break; case FIO_RDMA_CHA_SEND: r_io_u_d = io_us[i]->engine_data; r_io_u_d->sq_wr.opcode = IBV_WR_SEND; r_io_u_d->sq_wr.send_flags = IBV_SEND_SIGNALED; break; default: log_err("fio: unknown rdma protocol - %d\n", rd->rdma_protocol); break; } if (ibv_post_send(rd->qp, &r_io_u_d->sq_wr, &bad_wr) != 0) { log_err("fio: ibv_post_send fail\n"); return -1; } dprint_io_u(io_us[i], "fio_rdmaio_send"); } /* wait for completion rdma_poll_wait(td, comp_opcode); */ return i; } static int fio_rdmaio_recv(struct thread_data *td, struct io_u **io_us, unsigned int nr) { struct rdmaio_data *rd = td->io_ops->data; struct ibv_recv_wr *bad_wr; struct rdma_io_u_data *r_io_u_d; int i; i = 0; if (rd->rdma_protocol == FIO_RDMA_CHA_RECV) { /* post io_u into recv queue */ for (i = 0; i < nr; i++) { r_io_u_d = io_us[i]->engine_data; if (ibv_post_recv(rd->qp, &r_io_u_d->rq_wr, &bad_wr) != 0) { log_err("fio: ibv_post_recv fail\n"); return 1; } } } else if ((rd->rdma_protocol == FIO_RDMA_MEM_READ) || (rd->rdma_protocol == FIO_RDMA_MEM_WRITE)) { /* re-post the rq_wr */ if (ibv_post_recv(rd->qp, &rd->rq_wr, &bad_wr) != 0) { log_err("fio: ibv_post_recv fail\n"); return 1; } rdma_poll_wait(td, IBV_WC_RECV); dprint(FD_IO, "fio: recv FINISH message\n"); td->done = 1; return 0; } return i; } static int fio_rdmaio_queue(struct thread_data *td, struct io_u *io_u) { struct rdmaio_data *rd = td->io_ops->data; fio_ro_check(td, io_u); if (rd->io_u_queued_nr == (int)td->o.iodepth) return FIO_Q_BUSY; rd->io_us_queued[rd->io_u_queued_nr] = io_u; rd->io_u_queued_nr++; dprint_io_u(io_u, "fio_rdmaio_queue"); return FIO_Q_QUEUED; } static void fio_rdmaio_queued(struct thread_data *td, struct io_u **io_us, unsigned int nr) { struct rdmaio_data *rd = td->io_ops->data; struct timeval now; unsigned int i; if (!fio_fill_issue_time(td)) return; fio_gettime(&now, NULL); for (i = 0; i < nr; i++) { struct io_u *io_u = io_us[i]; /* queued -> flight */ rd->io_us_flight[rd->io_u_flight_nr] = io_u; rd->io_u_flight_nr++; memcpy(&io_u->issue_time, &now, sizeof(now)); io_u_queued(td, io_u); } } static int fio_rdmaio_commit(struct thread_data *td) { struct rdmaio_data *rd = td->io_ops->data; struct io_u **io_us; int ret; if (!rd->io_us_queued) return 0; io_us = rd->io_us_queued; do { /* RDMA_WRITE or RDMA_READ */ if (rd->is_client) ret = fio_rdmaio_send(td, io_us, rd->io_u_queued_nr); else if (!rd->is_client) ret = fio_rdmaio_recv(td, io_us, rd->io_u_queued_nr); else ret = 0; /* must be a SYNC */ if (ret > 0) { fio_rdmaio_queued(td, io_us, ret); io_u_mark_submit(td, ret); rd->io_u_queued_nr -= ret; io_us += ret; ret = 0; } else break; } while (rd->io_u_queued_nr); return ret; } static int fio_rdmaio_connect(struct thread_data *td, struct fio_file *f) { struct rdmaio_data *rd = td->io_ops->data; struct rdma_conn_param conn_param; struct ibv_send_wr *bad_wr; memset(&conn_param, 0, sizeof(conn_param)); conn_param.responder_resources = 1; conn_param.initiator_depth = 1; conn_param.retry_count = 10; if (rdma_connect(rd->cm_id, &conn_param) != 0) { log_err("fio: rdma_connect fail\n"); return 1; } if (get_next_channel_event (td, rd->cm_channel, RDMA_CM_EVENT_ESTABLISHED) != 0) { log_err("fio: wait for RDMA_CM_EVENT_ESTABLISHED\n"); return 1; } /* send task request */ rd->send_buf.mode = htonl(rd->rdma_protocol); rd->send_buf.nr = htonl(td->o.iodepth); if (ibv_post_send(rd->qp, &rd->sq_wr, &bad_wr) != 0) { log_err("fio: ibv_post_send fail"); return 1; } rdma_poll_wait(td, IBV_WC_SEND); /* wait for remote MR info from server side */ rdma_poll_wait(td, IBV_WC_RECV); /* In SEND/RECV test, it's a good practice to setup the iodepth of * of the RECV side deeper than that of the SEND side to * avoid RNR (receiver not ready) error. The * SEND side may send so many unsolicited message before * RECV side commits sufficient recv buffers into recv queue. * This may lead to RNR error. Here, SEND side pauses for a while * during which RECV side commits sufficient recv buffers. */ usleep(500000); return 0; } static int fio_rdmaio_accept(struct thread_data *td, struct fio_file *f) { struct rdmaio_data *rd = td->io_ops->data; struct rdma_conn_param conn_param; struct ibv_send_wr *bad_wr; /* rdma_accept() - then wait for accept success */ memset(&conn_param, 0, sizeof(conn_param)); conn_param.responder_resources = 1; conn_param.initiator_depth = 1; if (rdma_accept(rd->child_cm_id, &conn_param) != 0) { log_err("fio: rdma_accept\n"); return 1; } if (get_next_channel_event (td, rd->cm_channel, RDMA_CM_EVENT_ESTABLISHED) != 0) { log_err("fio: wait for RDMA_CM_EVENT_ESTABLISHED\n"); return 1; } /* wait for request */ rdma_poll_wait(td, IBV_WC_RECV); if (ibv_post_send(rd->qp, &rd->sq_wr, &bad_wr) != 0) { log_err("fio: ibv_post_send fail"); return 1; } rdma_poll_wait(td, IBV_WC_SEND); return 0; } static int fio_rdmaio_open_file(struct thread_data *td, struct fio_file *f) { if (td_read(td)) return fio_rdmaio_accept(td, f); else return fio_rdmaio_connect(td, f); } static int fio_rdmaio_close_file(struct thread_data *td, struct fio_file *f) { struct rdmaio_data *rd = td->io_ops->data; struct ibv_send_wr *bad_wr; /* unregister rdma buffer */ /* * Client sends notification to the server side */ /* refer to: http://linux.die.net/man/7/rdma_cm */ if ((rd->is_client == 1) && ((rd->rdma_protocol == FIO_RDMA_MEM_WRITE) || (rd->rdma_protocol == FIO_RDMA_MEM_READ))) { if (ibv_post_send(rd->qp, &rd->sq_wr, &bad_wr) != 0) { log_err("fio: ibv_post_send fail"); return 1; } dprint(FD_IO, "fio: close infomation sent success\n"); rdma_poll_wait(td, IBV_WC_SEND); } if (rd->is_client == 1) rdma_disconnect(rd->cm_id); else { rdma_disconnect(rd->child_cm_id); #if 0 rdma_disconnect(rd->cm_id); #endif } #if 0 if (get_next_channel_event(td, rd->cm_channel, RDMA_CM_EVENT_DISCONNECTED) != 0) { log_err("fio: wait for RDMA_CM_EVENT_DISCONNECTED\n"); return 1; } #endif ibv_destroy_cq(rd->cq); ibv_destroy_qp(rd->qp); if (rd->is_client == 1) rdma_destroy_id(rd->cm_id); else { rdma_destroy_id(rd->child_cm_id); rdma_destroy_id(rd->cm_id); } ibv_destroy_comp_channel(rd->channel); ibv_dealloc_pd(rd->pd); return 0; } static int fio_rdmaio_setup_connect(struct thread_data *td, const char *host, unsigned short port) { struct rdmaio_data *rd = td->io_ops->data; struct ibv_recv_wr *bad_wr; int err; rd->addr.sin_family = AF_INET; rd->addr.sin_port = htons(port); if (inet_aton(host, &rd->addr.sin_addr) != 1) { struct hostent *hent; hent = gethostbyname(host); if (!hent) { td_verror(td, errno, "gethostbyname"); return 1; } memcpy(&rd->addr.sin_addr, hent->h_addr, 4); } /* resolve route */ err = rdma_resolve_addr(rd->cm_id, NULL, (struct sockaddr *)&rd->addr, 2000); if (err != 0) { log_err("fio: rdma_resolve_addr: %d\n", err); return 1; } err = get_next_channel_event(td, rd->cm_channel, RDMA_CM_EVENT_ADDR_RESOLVED); if (err != 0) { log_err("fio: get_next_channel_event: %d\n", err); return 1; } /* resolve route */ err = rdma_resolve_route(rd->cm_id, 2000); if (err != 0) { log_err("fio: rdma_resolve_route: %d\n", err); return 1; } err = get_next_channel_event(td, rd->cm_channel, RDMA_CM_EVENT_ROUTE_RESOLVED); if (err != 0) { log_err("fio: get_next_channel_event: %d\n", err); return 1; } /* create qp and buffer */ if (fio_rdmaio_setup_qp(td) != 0) return 1; if (fio_rdmaio_setup_control_msg_buffers(td) != 0) return 1; /* post recv buf */ err = ibv_post_recv(rd->qp, &rd->rq_wr, &bad_wr); if (err != 0) { log_err("fio: ibv_post_recv fail: %d\n", err); return 1; } return 0; } static int fio_rdmaio_setup_listen(struct thread_data *td, short port) { struct rdmaio_data *rd = td->io_ops->data; struct ibv_recv_wr *bad_wr; rd->addr.sin_family = AF_INET; rd->addr.sin_addr.s_addr = htonl(INADDR_ANY); rd->addr.sin_port = htons(port); /* rdma_listen */ if (rdma_bind_addr(rd->cm_id, (struct sockaddr *)&rd->addr) != 0) { log_err("fio: rdma_bind_addr fail\n"); return 1; } if (rdma_listen(rd->cm_id, 3) != 0) { log_err("fio: rdma_listen fail\n"); return 1; } /* wait for CONNECT_REQUEST */ if (get_next_channel_event (td, rd->cm_channel, RDMA_CM_EVENT_CONNECT_REQUEST) != 0) { log_err("fio: wait for RDMA_CM_EVENT_CONNECT_REQUEST\n"); return 1; } if (fio_rdmaio_setup_qp(td) != 0) return 1; if (fio_rdmaio_setup_control_msg_buffers(td) != 0) return 1; /* post recv buf */ if (ibv_post_recv(rd->qp, &rd->rq_wr, &bad_wr) != 0) { log_err("fio: ibv_post_recv fail\n"); return 1; } return 0; } static int check_set_rlimits(struct thread_data *td) { #ifdef CONFIG_RLIMIT_MEMLOCK struct rlimit rl; /* check RLIMIT_MEMLOCK */ if (getrlimit(RLIMIT_MEMLOCK, &rl) != 0) { log_err("fio: getrlimit fail: %d(%s)\n", errno, strerror(errno)); return 1; } /* soft limit */ if ((rl.rlim_cur != RLIM_INFINITY) && (rl.rlim_cur < td->orig_buffer_size)) { log_err("fio: soft RLIMIT_MEMLOCK is: %" PRId64 "\n", rl.rlim_cur); log_err("fio: total block size is: %zd\n", td->orig_buffer_size); /* try to set larger RLIMIT_MEMLOCK */ rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_MEMLOCK, &rl) != 0) { log_err("fio: setrlimit fail: %d(%s)\n", errno, strerror(errno)); log_err("fio: you may try enlarge MEMLOCK by root\n"); log_err("# ulimit -l unlimited\n"); return 1; } } #endif return 0; } static int fio_rdmaio_init(struct thread_data *td) { struct rdmaio_data *rd = td->io_ops->data; unsigned int max_bs; unsigned int port; char host[64], buf[128]; char *sep, *portp, *modep; int ret, i; if (td_rw(td)) { log_err("fio: rdma connections must be read OR write\n"); return 1; } if (td_random(td)) { log_err("fio: RDMA network IO can't be random\n"); return 1; } if (check_set_rlimits(td)) return 1; strcpy(buf, td->o.filename); sep = strchr(buf, '/'); if (!sep) goto bad_host; *sep = '\0'; sep++; strcpy(host, buf); if (!strlen(host)) goto bad_host; modep = NULL; portp = sep; sep = strchr(portp, '/'); if (sep) { *sep = '\0'; modep = sep + 1; } port = strtol(portp, NULL, 10); if (!port || port > 65535) goto bad_host; if (modep) { if (!strncmp("rdma_write", modep, strlen(modep)) || !strncmp("RDMA_WRITE", modep, strlen(modep))) rd->rdma_protocol = FIO_RDMA_MEM_WRITE; else if (!strncmp("rdma_read", modep, strlen(modep)) || !strncmp("RDMA_READ", modep, strlen(modep))) rd->rdma_protocol = FIO_RDMA_MEM_READ; else if (!strncmp("send", modep, strlen(modep)) || !strncmp("SEND", modep, strlen(modep))) rd->rdma_protocol = FIO_RDMA_CHA_SEND; else goto bad_host; } else rd->rdma_protocol = FIO_RDMA_MEM_WRITE; rd->cq_event_num = 0; rd->cm_channel = rdma_create_event_channel(); if (!rd->cm_channel) { log_err("fio: rdma_create_event_channel fail\n"); return 1; } ret = rdma_create_id(rd->cm_channel, &rd->cm_id, rd, RDMA_PS_TCP); if (ret) { log_err("fio: rdma_create_id fail\n"); return 1; } if ((rd->rdma_protocol == FIO_RDMA_MEM_WRITE) || (rd->rdma_protocol == FIO_RDMA_MEM_READ)) { rd->rmt_us = malloc(FIO_RDMA_MAX_IO_DEPTH * sizeof(struct remote_u)); memset(rd->rmt_us, 0, FIO_RDMA_MAX_IO_DEPTH * sizeof(struct remote_u)); rd->rmt_nr = 0; } rd->io_us_queued = malloc(td->o.iodepth * sizeof(struct io_u *)); memset(rd->io_us_queued, 0, td->o.iodepth * sizeof(struct io_u *)); rd->io_u_queued_nr = 0; rd->io_us_flight = malloc(td->o.iodepth * sizeof(struct io_u *)); memset(rd->io_us_flight, 0, td->o.iodepth * sizeof(struct io_u *)); rd->io_u_flight_nr = 0; rd->io_us_completed = malloc(td->o.iodepth * sizeof(struct io_u *)); memset(rd->io_us_completed, 0, td->o.iodepth * sizeof(struct io_u *)); rd->io_u_completed_nr = 0; if (td_read(td)) { /* READ as the server */ rd->is_client = 0; /* server rd->rdma_buf_len will be setup after got request */ ret = fio_rdmaio_setup_listen(td, port); } else { /* WRITE as the client */ rd->is_client = 1; ret = fio_rdmaio_setup_connect(td, host, port); } max_bs = max(td->o.max_bs[DDIR_READ], td->o.max_bs[DDIR_WRITE]); /* register each io_u in the free list */ for (i = 0; i < td->io_u_freelist.nr; i++) { struct io_u *io_u = td->io_u_freelist.io_us[i]; io_u->engine_data = malloc(sizeof(struct rdma_io_u_data)); memset(io_u->engine_data, 0, sizeof(struct rdma_io_u_data)); ((struct rdma_io_u_data *)io_u->engine_data)->wr_id = i; io_u->mr = ibv_reg_mr(rd->pd, io_u->buf, max_bs, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ | IBV_ACCESS_REMOTE_WRITE); if (io_u->mr == NULL) { log_err("fio: ibv_reg_mr io_u failed\n"); return 1; } rd->send_buf.rmt_us[i].buf = htonll((uint64_t) (unsigned long)io_u->buf); rd->send_buf.rmt_us[i].rkey = htonl(io_u->mr->rkey); rd->send_buf.rmt_us[i].size = htonl(max_bs); #if 0 log_info("fio: Send rkey %x addr %" PRIx64 " len %d to client\n", io_u->mr->rkey, io_u->buf, max_bs); */ #endif } rd->send_buf.nr = htonl(i); return ret; bad_host: log_err("fio: bad rdma host/port/protocol: %s\n", td->o.filename); return 1; } static void fio_rdmaio_cleanup(struct thread_data *td) { struct rdmaio_data *rd = td->io_ops->data; if (rd) free(rd); } static int fio_rdmaio_setup(struct thread_data *td) { struct rdmaio_data *rd; if (!td->io_ops->data) { rd = malloc(sizeof(*rd)); memset(rd, 0, sizeof(*rd)); init_rand_seed(&rd->rand_state, (unsigned int) GOLDEN_RATIO_PRIME); td->io_ops->data = rd; } return 0; } static struct ioengine_ops ioengine_rw = { .name = "rdma", .version = FIO_IOOPS_VERSION, .setup = fio_rdmaio_setup, .init = fio_rdmaio_init, .prep = fio_rdmaio_prep, .queue = fio_rdmaio_queue, .commit = fio_rdmaio_commit, .getevents = fio_rdmaio_getevents, .event = fio_rdmaio_event, .cleanup = fio_rdmaio_cleanup, .open_file = fio_rdmaio_open_file, .close_file = fio_rdmaio_close_file, .flags = FIO_DISKLESSIO | FIO_UNIDIR | FIO_PIPEIO, }; static void fio_init fio_rdmaio_register(void) { register_ioengine(&ioengine_rw); } static void fio_exit fio_rdmaio_unregister(void) { unregister_ioengine(&ioengine_rw); } fio-2.1.3/engines/sg.c000066400000000000000000000215321222032232000144660ustar00rootroot00000000000000/* * sg engine * * IO engine that uses the Linux SG v3 interface to talk to SCSI devices * */ #include #include #include #include #include #include #include "../fio.h" #ifdef FIO_HAVE_SGIO struct sgio_cmd { unsigned char cdb[10]; int nr; }; struct sgio_data { struct sgio_cmd *cmds; struct io_u **events; struct pollfd *pfds; int *fd_flags; void *sgbuf; unsigned int bs; int type_checked; }; static void sgio_hdr_init(struct sgio_data *sd, struct sg_io_hdr *hdr, struct io_u *io_u, int fs) { struct sgio_cmd *sc = &sd->cmds[io_u->index]; memset(hdr, 0, sizeof(*hdr)); memset(sc->cdb, 0, sizeof(sc->cdb)); hdr->interface_id = 'S'; hdr->cmdp = sc->cdb; hdr->cmd_len = sizeof(sc->cdb); hdr->pack_id = io_u->index; hdr->usr_ptr = io_u; if (fs) { hdr->dxferp = io_u->xfer_buf; hdr->dxfer_len = io_u->xfer_buflen; } } static int pollin_events(struct pollfd *pfds, int fds) { int i; for (i = 0; i < fds; i++) if (pfds[i].revents & POLLIN) return 1; return 0; } static int fio_sgio_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec fio_unused *t) { struct sgio_data *sd = td->io_ops->data; int left = max, ret, r = 0; void *buf = sd->sgbuf; unsigned int i, events; struct fio_file *f; /* * Fill in the file descriptors */ for_each_file(td, f, i) { /* * don't block for min events == 0 */ if (!min) { sd->fd_flags[i] = fcntl(f->fd, F_GETFL); fcntl(f->fd, F_SETFL, sd->fd_flags[i] | O_NONBLOCK); } sd->pfds[i].fd = f->fd; sd->pfds[i].events = POLLIN; } while (left) { void *p; do { if (!min) break; ret = poll(sd->pfds, td->o.nr_files, -1); if (ret < 0) { if (!r) r = -errno; td_verror(td, errno, "poll"); break; } else if (!ret) continue; if (pollin_events(sd->pfds, td->o.nr_files)) break; } while (1); if (r < 0) break; re_read: p = buf; events = 0; for_each_file(td, f, i) { ret = read(f->fd, p, left * sizeof(struct sg_io_hdr)); if (ret < 0) { if (errno == EAGAIN) continue; r = -errno; td_verror(td, errno, "read"); break; } else if (ret) { p += ret; events += ret / sizeof(struct sg_io_hdr); } } if (r < 0) break; if (!events) { usleep(1000); goto re_read; } left -= events; r += events; for (i = 0; i < events; i++) { struct sg_io_hdr *hdr = (struct sg_io_hdr *) buf + i; sd->events[i] = hdr->usr_ptr; } } if (!min) { for_each_file(td, f, i) fcntl(f->fd, F_SETFL, sd->fd_flags[i]); } return r; } static int fio_sgio_ioctl_doio(struct thread_data *td, struct fio_file *f, struct io_u *io_u) { struct sgio_data *sd = td->io_ops->data; struct sg_io_hdr *hdr = &io_u->hdr; int ret; sd->events[0] = io_u; ret = ioctl(f->fd, SG_IO, hdr); if (ret < 0) return ret; return FIO_Q_COMPLETED; } static int fio_sgio_rw_doio(struct fio_file *f, struct io_u *io_u, int do_sync) { struct sg_io_hdr *hdr = &io_u->hdr; int ret; ret = write(f->fd, hdr, sizeof(*hdr)); if (ret < 0) return ret; if (do_sync) { ret = read(f->fd, hdr, sizeof(*hdr)); if (ret < 0) return ret; return FIO_Q_COMPLETED; } return FIO_Q_QUEUED; } static int fio_sgio_doio(struct thread_data *td, struct io_u *io_u, int do_sync) { struct fio_file *f = io_u->file; if (f->filetype == FIO_TYPE_BD) return fio_sgio_ioctl_doio(td, f, io_u); return fio_sgio_rw_doio(f, io_u, do_sync); } static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u) { struct sg_io_hdr *hdr = &io_u->hdr; struct sgio_data *sd = td->io_ops->data; int nr_blocks, lba; if (io_u->xfer_buflen & (sd->bs - 1)) { log_err("read/write not sector aligned\n"); return EINVAL; } if (io_u->ddir == DDIR_READ) { sgio_hdr_init(sd, hdr, io_u, 1); hdr->dxfer_direction = SG_DXFER_FROM_DEV; hdr->cmdp[0] = 0x28; } else if (io_u->ddir == DDIR_WRITE) { sgio_hdr_init(sd, hdr, io_u, 1); hdr->dxfer_direction = SG_DXFER_TO_DEV; hdr->cmdp[0] = 0x2a; } else { sgio_hdr_init(sd, hdr, io_u, 0); hdr->dxfer_direction = SG_DXFER_NONE; hdr->cmdp[0] = 0x35; } if (hdr->dxfer_direction != SG_DXFER_NONE) { nr_blocks = io_u->xfer_buflen / sd->bs; lba = io_u->offset / sd->bs; hdr->cmdp[2] = (unsigned char) ((lba >> 24) & 0xff); hdr->cmdp[3] = (unsigned char) ((lba >> 16) & 0xff); hdr->cmdp[4] = (unsigned char) ((lba >> 8) & 0xff); hdr->cmdp[5] = (unsigned char) (lba & 0xff); hdr->cmdp[7] = (unsigned char) ((nr_blocks >> 8) & 0xff); hdr->cmdp[8] = (unsigned char) (nr_blocks & 0xff); } return 0; } static int fio_sgio_queue(struct thread_data *td, struct io_u *io_u) { struct sg_io_hdr *hdr = &io_u->hdr; int ret, do_sync = 0; fio_ro_check(td, io_u); if (td->o.sync_io || td->o.odirect || ddir_sync(io_u->ddir)) do_sync = 1; ret = fio_sgio_doio(td, io_u, do_sync); if (ret < 0) io_u->error = errno; else if (hdr->status) { io_u->resid = hdr->resid; io_u->error = EIO; } if (io_u->error) { td_verror(td, io_u->error, "xfer"); return FIO_Q_COMPLETED; } return ret; } static struct io_u *fio_sgio_event(struct thread_data *td, int event) { struct sgio_data *sd = td->io_ops->data; return sd->events[event]; } static int fio_sgio_get_bs(struct thread_data *td, unsigned int *bs) { struct sgio_data *sd = td->io_ops->data; struct io_u io_u; struct sg_io_hdr *hdr; unsigned char buf[8]; int ret; memset(&io_u, 0, sizeof(io_u)); io_u.file = td->files[0]; hdr = &io_u.hdr; sgio_hdr_init(sd, hdr, &io_u, 0); memset(buf, 0, sizeof(buf)); hdr->cmdp[0] = 0x25; hdr->dxfer_direction = SG_DXFER_FROM_DEV; hdr->dxferp = buf; hdr->dxfer_len = sizeof(buf); ret = fio_sgio_doio(td, &io_u, 1); if (ret) return ret; *bs = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; return 0; } static void fio_sgio_cleanup(struct thread_data *td) { struct sgio_data *sd = td->io_ops->data; if (sd) { free(sd->events); free(sd->cmds); free(sd->fd_flags); free(sd->pfds); free(sd->sgbuf); free(sd); } } static int fio_sgio_init(struct thread_data *td) { struct sgio_data *sd; sd = malloc(sizeof(*sd)); memset(sd, 0, sizeof(*sd)); sd->cmds = malloc(td->o.iodepth * sizeof(struct sgio_cmd)); memset(sd->cmds, 0, td->o.iodepth * sizeof(struct sgio_cmd)); sd->events = malloc(td->o.iodepth * sizeof(struct io_u *)); memset(sd->events, 0, td->o.iodepth * sizeof(struct io_u *)); sd->pfds = malloc(sizeof(struct pollfd) * td->o.nr_files); memset(sd->pfds, 0, sizeof(struct pollfd) * td->o.nr_files); sd->fd_flags = malloc(sizeof(int) * td->o.nr_files); memset(sd->fd_flags, 0, sizeof(int) * td->o.nr_files); sd->sgbuf = malloc(sizeof(struct sg_io_hdr) * td->o.iodepth); memset(sd->sgbuf, 0, sizeof(struct sg_io_hdr) * td->o.iodepth); td->io_ops->data = sd; /* * we want to do it, regardless of whether odirect is set or not */ td->o.override_sync = 1; return 0; } static int fio_sgio_type_check(struct thread_data *td, struct fio_file *f) { struct sgio_data *sd = td->io_ops->data; unsigned int bs; if (f->filetype == FIO_TYPE_BD) { if (ioctl(f->fd, BLKSSZGET, &bs) < 0) { td_verror(td, errno, "ioctl"); return 1; } } else if (f->filetype == FIO_TYPE_CHAR) { int version, ret; if (ioctl(f->fd, SG_GET_VERSION_NUM, &version) < 0) { td_verror(td, errno, "ioctl"); return 1; } ret = fio_sgio_get_bs(td, &bs); if (ret) return 1; } else { log_err("ioengine sg only works on block devices\n"); return 1; } sd->bs = bs; if (f->filetype == FIO_TYPE_BD) { td->io_ops->getevents = NULL; td->io_ops->event = NULL; } return 0; } static int fio_sgio_open(struct thread_data *td, struct fio_file *f) { struct sgio_data *sd = td->io_ops->data; int ret; ret = generic_open_file(td, f); if (ret) return ret; if (sd && !sd->type_checked && fio_sgio_type_check(td, f)) { ret = generic_close_file(td, f); return 1; } return 0; } static struct ioengine_ops ioengine = { .name = "sg", .version = FIO_IOOPS_VERSION, .init = fio_sgio_init, .prep = fio_sgio_prep, .queue = fio_sgio_queue, .getevents = fio_sgio_getevents, .event = fio_sgio_event, .cleanup = fio_sgio_cleanup, .open_file = fio_sgio_open, .close_file = generic_close_file, .get_file_size = generic_get_file_size, .flags = FIO_SYNCIO | FIO_RAWIO, }; #else /* FIO_HAVE_SGIO */ /* * When we have a proper configure system in place, we simply wont build * and install this io engine. For now install a crippled version that * just complains and fails to load. */ static int fio_sgio_init(struct thread_data fio_unused *td) { log_err("fio: ioengine sg not available\n"); return 1; } static struct ioengine_ops ioengine = { .name = "sg", .version = FIO_IOOPS_VERSION, .init = fio_sgio_init, }; #endif static void fio_init fio_sgio_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_sgio_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/skeleton_external.c000066400000000000000000000076331222032232000176110ustar00rootroot00000000000000/* * Skeleton for a sample external io engine * * Should be compiled with: * * gcc -Wall -O2 -g -shared -rdynamic -fPIC -o engine.o engine.c * */ #include #include #include #include #include #include "../fio.h" /* * The core of the module is identical to the ones included with fio, * read those. You cannot use register_ioengine() and unregister_ioengine() * for external modules, they should be gotten through dlsym() */ /* * The ->event() hook is called to match an event number with an io_u. * After the core has called ->getevents() and it has returned eg 3, * the ->event() hook must return the 3 events that have completed for * subsequent calls to ->event() with [0-2]. Required. */ static struct io_u *fio_skeleton_event(struct thread_data *td, int event) { return NULL; } /* * The ->getevents() hook is used to reap completion events from an async * io engine. It returns the number of completed events since the last call, * which may then be retrieved by calling the ->event() hook with the event * numbers. Required. */ static int fio_skeleton_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec *t) { return 0; } /* * The ->cancel() hook attempts to cancel the io_u. Only relevant for * async io engines, and need not be supported. */ static int fio_skeleton_cancel(struct thread_data *td, struct io_u *io_u) { return 0; } /* * The ->queue() hook is responsible for initiating io on the io_u * being passed in. If the io engine is a synchronous one, io may complete * before ->queue() returns. Required. * * The io engine must transfer in the direction noted by io_u->ddir * to the buffer pointed to by io_u->xfer_buf for as many bytes as * io_u->xfer_buflen. Residual data count may be set in io_u->resid * for a short read/write. */ static int fio_skeleton_queue(struct thread_data *td, struct io_u *io_u) { /* * Double sanity check to catch errant write on a readonly setup */ fio_ro_check(td, io_u); /* * Could return FIO_Q_QUEUED for a queued request, * FIO_Q_COMPLETED for a completed request, and FIO_Q_BUSY * if we could queue no more at this point (you'd have to * define ->commit() to handle that. */ return FIO_Q_COMPLETED; } /* * The ->prep() function is called for each io_u prior to being submitted * with ->queue(). This hook allows the io engine to perform any * preparatory actions on the io_u, before being submitted. Not required. */ static int fio_skeleton_prep(struct thread_data *td, struct io_u *io_u) { return 0; } /* * The init function is called once per thread/process, and should set up * any structures that this io engine requires to keep track of io. Not * required. */ static int fio_skeleton_init(struct thread_data *td) { return 0; } /* * This is paired with the ->init() funtion and is called when a thread is * done doing io. Should tear down anything setup by the ->init() function. * Not required. */ static void fio_skeleton_cleanup(struct thread_data *td) { } /* * Hook for opening the given file. Unless the engine has special * needs, it usually just provides generic_file_open() as the handler. */ static int fio_skeleton_open(struct thread_data *td, struct fio_file *f) { return generic_file_open(td, f); } /* * Hook for closing a file. See fio_skeleton_open(). */ static int fio_skeleton_close(struct thread_data *td, struct fio_file *f) { generic_file_close(td, f); } /* * Note that the structure is exported, so that fio can get it via * dlsym(..., "ioengine"); */ struct ioengine_ops ioengine = { .name = "engine_name", .version = FIO_IOOPS_VERSION, .init = fio_skeleton_init, .prep = fio_skeleton_prep, .queue = fio_skeleton_queue, .cancel = fio_skeleton_cancel, .getevents = fio_skeleton_getevents, .event = fio_skeleton_event, .cleanup = fio_skeleton_cleanup, .open_file = fio_skeleton_open, .close_file = fio_skeleton_close, }; fio-2.1.3/engines/solarisaio.c000066400000000000000000000114511222032232000162210ustar00rootroot00000000000000/* * Native Solaris async IO engine * */ #include #include #include #include #include #include "../fio.h" #include struct solarisaio_data { struct io_u **aio_events; unsigned int aio_pending; unsigned int nr; unsigned int max_depth; }; static int fio_solarisaio_cancel(struct thread_data fio_unused *td, struct io_u *io_u) { return aiocancel(&io_u->resultp); } static int fio_solarisaio_prep(struct thread_data fio_unused *td, struct io_u *io_u) { struct solarisaio_data *sd = td->io_ops->data; io_u->resultp.aio_return = AIO_INPROGRESS; io_u->engine_data = sd; return 0; } static void wait_for_event(struct timeval *tv) { struct solarisaio_data *sd; struct io_u *io_u; aio_result_t *res; res = aiowait(tv); if (res == (aio_result_t *) -1) { int err = errno; if (err != EINVAL) { log_err("fio: solarisaio got %d in aiowait\n", err); exit(err); } return; } else if (!res) return; io_u = container_of(res, struct io_u, resultp); sd = io_u->engine_data; if (io_u->resultp.aio_return >= 0) { io_u->resid = io_u->xfer_buflen - io_u->resultp.aio_return; io_u->error = 0; } else io_u->error = io_u->resultp.aio_errno; /* * For SIGIO, we need a write barrier between the two, so that * the ->aio_pending store is seen after the ->aio_events store */ sd->aio_events[sd->aio_pending] = io_u; write_barrier(); sd->aio_pending++; sd->nr--; } static int fio_solarisaio_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec *t) { struct solarisaio_data *sd = td->io_ops->data; struct timeval tv; int ret; if (!min || !t) { tv.tv_sec = 0; tv.tv_usec = 0; } else { tv.tv_sec = t->tv_sec; tv.tv_usec = t->tv_nsec / 1000; } while (sd->aio_pending < min) wait_for_event(&tv); /* * should be OK without locking, as int operations should be atomic */ ret = sd->aio_pending; sd->aio_pending -= ret; return ret; } static struct io_u *fio_solarisaio_event(struct thread_data *td, int event) { struct solarisaio_data *sd = td->io_ops->data; return sd->aio_events[event]; } static int fio_solarisaio_queue(struct thread_data fio_unused *td, struct io_u *io_u) { struct solarisaio_data *sd = td->io_ops->data; struct fio_file *f = io_u->file; off_t off; int ret; fio_ro_check(td, io_u); if (io_u->ddir == DDIR_SYNC) { if (sd->nr) return FIO_Q_BUSY; if (fsync(f->fd) < 0) io_u->error = errno; return FIO_Q_COMPLETED; } if (io_u->ddir == DDIR_DATASYNC) { if (sd->nr) return FIO_Q_BUSY; if (fdatasync(f->fd) < 0) io_u->error = errno; return FIO_Q_COMPLETED; } if (sd->nr == sd->max_depth) return FIO_Q_BUSY; off = io_u->offset; if (io_u->ddir == DDIR_READ) ret = aioread(f->fd, io_u->xfer_buf, io_u->xfer_buflen, off, SEEK_SET, &io_u->resultp); else ret = aiowrite(f->fd, io_u->xfer_buf, io_u->xfer_buflen, off, SEEK_SET, &io_u->resultp); if (ret) { io_u->error = errno; td_verror(td, io_u->error, "xfer"); return FIO_Q_COMPLETED; } sd->nr++; return FIO_Q_QUEUED; } static void fio_solarisaio_cleanup(struct thread_data *td) { struct solarisaio_data *sd = td->io_ops->data; if (sd) { free(sd->aio_events); free(sd); } } /* * Set USE_SIGNAL_COMPLETIONS to use SIGIO as completion events. */ #ifdef USE_SIGNAL_COMPLETIONS static void fio_solarisaio_sigio(int sig) { wait_for_event(NULL); } static void fio_solarisaio_init_sigio(void) { struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = fio_solarisaio_sigio; act.sa_flags = SA_RESTART; sigaction(SIGIO, &act, NULL); } #endif static int fio_solarisaio_init(struct thread_data *td) { struct solarisaio_data *sd = malloc(sizeof(*sd)); unsigned int max_depth; max_depth = td->o.iodepth; if (max_depth > MAXASYNCHIO) { max_depth = MAXASYNCHIO; log_info("fio: lower depth to %d due to OS constraints\n", max_depth); } memset(sd, 0, sizeof(*sd)); sd->aio_events = malloc(max_depth * sizeof(struct io_u *)); memset(sd->aio_events, 0, max_depth * sizeof(struct io_u *)); sd->max_depth = max_depth; #ifdef USE_SIGNAL_COMPLETIONS fio_solarisaio_init_sigio(); #endif td->io_ops->data = sd; return 0; } static struct ioengine_ops ioengine = { .name = "solarisaio", .version = FIO_IOOPS_VERSION, .init = fio_solarisaio_init, .prep = fio_solarisaio_prep, .queue = fio_solarisaio_queue, .cancel = fio_solarisaio_cancel, .getevents = fio_solarisaio_getevents, .event = fio_solarisaio_event, .cleanup = fio_solarisaio_cleanup, .open_file = generic_open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, }; static void fio_init fio_solarisaio_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_solarisaio_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/splice.c000066400000000000000000000145071222032232000153400ustar00rootroot00000000000000/* * splice engine * * IO engine that transfers data by doing splices to/from pipes and * the files. * */ #include #include #include #include #include #include #include #include "../fio.h" struct spliceio_data { int pipe[2]; int vmsplice_to_user; int vmsplice_to_user_map; }; /* * vmsplice didn't use to support splicing to user space, this is the old * variant of getting that job done. Doesn't make a lot of sense, but it * uses splices to move data from the source into a pipe. */ static int fio_splice_read_old(struct thread_data *td, struct io_u *io_u) { struct spliceio_data *sd = td->io_ops->data; struct fio_file *f = io_u->file; int ret, ret2, buflen; off_t offset; void *p; offset = io_u->offset; buflen = io_u->xfer_buflen; p = io_u->xfer_buf; while (buflen) { int this_len = buflen; if (this_len > SPLICE_DEF_SIZE) this_len = SPLICE_DEF_SIZE; ret = splice(f->fd, &offset, sd->pipe[1], NULL, this_len, SPLICE_F_MORE); if (ret < 0) { if (errno == ENODATA || errno == EAGAIN) continue; return -errno; } buflen -= ret; while (ret) { ret2 = read(sd->pipe[0], p, ret); if (ret2 < 0) return -errno; ret -= ret2; p += ret2; } } return io_u->xfer_buflen; } /* * We can now vmsplice into userspace, so do the transfer by splicing into * a pipe and vmsplicing that into userspace. */ static int fio_splice_read(struct thread_data *td, struct io_u *io_u) { struct spliceio_data *sd = td->io_ops->data; struct fio_file *f = io_u->file; struct iovec iov; int ret , buflen, mmap_len; off_t offset; void *p, *map; ret = 0; offset = io_u->offset; mmap_len = buflen = io_u->xfer_buflen; if (sd->vmsplice_to_user_map) { map = mmap(io_u->xfer_buf, buflen, PROT_READ, MAP_PRIVATE|OS_MAP_ANON, 0, 0); if (map == MAP_FAILED) { td_verror(td, errno, "mmap io_u"); return -1; } p = map; } else { map = NULL; p = io_u->xfer_buf; } while (buflen) { int this_len = buflen; int flags = 0; if (this_len > SPLICE_DEF_SIZE) { this_len = SPLICE_DEF_SIZE; flags = SPLICE_F_MORE; } ret = splice(f->fd, &offset, sd->pipe[1], NULL, this_len,flags); if (ret < 0) { if (errno == ENODATA || errno == EAGAIN) continue; td_verror(td, errno, "splice-from-fd"); break; } buflen -= ret; iov.iov_base = p; iov.iov_len = ret; while (iov.iov_len) { ret = vmsplice(sd->pipe[0], &iov, 1, SPLICE_F_MOVE); if (ret < 0) { if (errno == EFAULT && sd->vmsplice_to_user_map) { sd->vmsplice_to_user_map = 0; munmap(map, mmap_len); map = NULL; p = io_u->xfer_buf; iov.iov_base = p; continue; } if (errno == EBADF) { ret = -EBADF; break; } td_verror(td, errno, "vmsplice"); break; } else if (!ret) { td_verror(td, ENODATA, "vmsplice"); ret = -1; break; } iov.iov_len -= ret; iov.iov_base += ret; p += ret; } if (ret < 0) break; } if (sd->vmsplice_to_user_map && munmap(map, mmap_len) < 0) { td_verror(td, errno, "munnap io_u"); return -1; } if (ret < 0) return ret; return io_u->xfer_buflen; } /* * For splice writing, we can vmsplice our data buffer directly into a * pipe and then splice that to a file. */ static int fio_splice_write(struct thread_data *td, struct io_u *io_u) { struct spliceio_data *sd = td->io_ops->data; struct iovec iov = { .iov_base = io_u->xfer_buf, .iov_len = io_u->xfer_buflen, }; struct pollfd pfd = { .fd = sd->pipe[1], .events = POLLOUT, }; struct fio_file *f = io_u->file; off_t off = io_u->offset; int ret, ret2; while (iov.iov_len) { if (poll(&pfd, 1, -1) < 0) return errno; ret = vmsplice(sd->pipe[1], &iov, 1, SPLICE_F_NONBLOCK); if (ret < 0) return -errno; iov.iov_len -= ret; iov.iov_base += ret; while (ret) { ret2 = splice(sd->pipe[0], NULL, f->fd, &off, ret, 0); if (ret2 < 0) return -errno; ret -= ret2; } } return io_u->xfer_buflen; } static int fio_spliceio_queue(struct thread_data *td, struct io_u *io_u) { struct spliceio_data *sd = td->io_ops->data; int ret = 0; fio_ro_check(td, io_u); if (io_u->ddir == DDIR_READ) { if (sd->vmsplice_to_user) { ret = fio_splice_read(td, io_u); /* * This kernel doesn't support vmsplice to user * space. Reset the vmsplice_to_user flag, so that * we retry below and don't hit this path again. */ if (ret == -EBADF) sd->vmsplice_to_user = 0; } if (!sd->vmsplice_to_user) ret = fio_splice_read_old(td, io_u); } else if (io_u->ddir == DDIR_WRITE) ret = fio_splice_write(td, io_u); else if (io_u->ddir == DDIR_TRIM) ret = do_io_u_trim(td, io_u); else ret = do_io_u_sync(td, io_u); if (ret != (int) io_u->xfer_buflen) { if (ret >= 0) { io_u->resid = io_u->xfer_buflen - ret; io_u->error = 0; return FIO_Q_COMPLETED; } else io_u->error = errno; } if (io_u->error) { td_verror(td, io_u->error, "xfer"); if (io_u->error == EINVAL) log_err("fio: looks like splice doesn't work on this" " file system\n"); } return FIO_Q_COMPLETED; } static void fio_spliceio_cleanup(struct thread_data *td) { struct spliceio_data *sd = td->io_ops->data; if (sd) { close(sd->pipe[0]); close(sd->pipe[1]); free(sd); } } static int fio_spliceio_init(struct thread_data *td) { struct spliceio_data *sd = malloc(sizeof(*sd)); if (pipe(sd->pipe) < 0) { td_verror(td, errno, "pipe"); free(sd); return 1; } /* * Assume this work, we'll reset this if it doesn't */ sd->vmsplice_to_user = 1; /* * Works with "real" vmsplice to user, eg mapping pages directly. * Reset if we fail. */ sd->vmsplice_to_user_map = 1; /* * And if vmsplice_to_user works, we definitely need aligned * buffers. Just set ->odirect to force that. */ if (td_read(td)) td->o.mem_align = 1; td->io_ops->data = sd; return 0; } static struct ioengine_ops ioengine = { .name = "splice", .version = FIO_IOOPS_VERSION, .init = fio_spliceio_init, .queue = fio_spliceio_queue, .cleanup = fio_spliceio_cleanup, .open_file = generic_open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, .flags = FIO_SYNCIO | FIO_PIPEIO, }; static void fio_init fio_spliceio_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_spliceio_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/engines/sync.c000066400000000000000000000211101222032232000150210ustar00rootroot00000000000000/* * sync/psync engine * * IO engine that does regular read(2)/write(2) with lseek(2) to transfer * data and IO engine that does regular pread(2)/pwrite(2) to transfer data. * */ #include #include #include #include #include #include #include "../fio.h" /* * Sync engine uses engine_data to store last offset */ #define LAST_POS(f) ((f)->engine_data) struct syncio_data { struct iovec *iovecs; struct io_u **io_us; unsigned int queued; unsigned int events; unsigned long queued_bytes; unsigned long long last_offset; struct fio_file *last_file; enum fio_ddir last_ddir; }; static int fio_syncio_prep(struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; if (!ddir_rw(io_u->ddir)) return 0; if (LAST_POS(f) != -1ULL && LAST_POS(f) == io_u->offset) return 0; if (lseek(f->fd, io_u->offset, SEEK_SET) == -1) { td_verror(td, errno, "lseek"); return 1; } return 0; } static int fio_io_end(struct thread_data *td, struct io_u *io_u, int ret) { if (io_u->file && ret >= 0 && ddir_rw(io_u->ddir)) LAST_POS(io_u->file) = io_u->offset + ret; if (ret != (int) io_u->xfer_buflen) { if (ret >= 0) { io_u->resid = io_u->xfer_buflen - ret; io_u->error = 0; return FIO_Q_COMPLETED; } else io_u->error = errno; } if (io_u->error) td_verror(td, io_u->error, "xfer"); return FIO_Q_COMPLETED; } #ifdef CONFIG_PWRITEV static int fio_pvsyncio_queue(struct thread_data *td, struct io_u *io_u) { struct syncio_data *sd = td->io_ops->data; struct iovec *iov = &sd->iovecs[0]; struct fio_file *f = io_u->file; int ret; fio_ro_check(td, io_u); iov->iov_base = io_u->xfer_buf; iov->iov_len = io_u->xfer_buflen; if (io_u->ddir == DDIR_READ) ret = preadv(f->fd, iov, 1, io_u->offset); else if (io_u->ddir == DDIR_WRITE) ret = pwritev(f->fd, iov, 1, io_u->offset); else if (io_u->ddir == DDIR_TRIM) { do_io_u_trim(td, io_u); return FIO_Q_COMPLETED; } else ret = do_io_u_sync(td, io_u); return fio_io_end(td, io_u, ret); } #endif static int fio_psyncio_queue(struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; int ret; fio_ro_check(td, io_u); if (io_u->ddir == DDIR_READ) ret = pread(f->fd, io_u->xfer_buf, io_u->xfer_buflen, io_u->offset); else if (io_u->ddir == DDIR_WRITE) ret = pwrite(f->fd, io_u->xfer_buf, io_u->xfer_buflen, io_u->offset); else if (io_u->ddir == DDIR_TRIM) { do_io_u_trim(td, io_u); return FIO_Q_COMPLETED; } else ret = do_io_u_sync(td, io_u); return fio_io_end(td, io_u, ret); } static int fio_syncio_queue(struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; int ret; fio_ro_check(td, io_u); if (io_u->ddir == DDIR_READ) ret = read(f->fd, io_u->xfer_buf, io_u->xfer_buflen); else if (io_u->ddir == DDIR_WRITE) ret = write(f->fd, io_u->xfer_buf, io_u->xfer_buflen); else if (io_u->ddir == DDIR_TRIM) { do_io_u_trim(td, io_u); return FIO_Q_COMPLETED; } else ret = do_io_u_sync(td, io_u); return fio_io_end(td, io_u, ret); } static int fio_vsyncio_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec fio_unused *t) { struct syncio_data *sd = td->io_ops->data; int ret; if (min) { ret = sd->events; sd->events = 0; } else ret = 0; dprint(FD_IO, "vsyncio_getevents: min=%d,max=%d: %d\n", min, max, ret); return ret; } static struct io_u *fio_vsyncio_event(struct thread_data *td, int event) { struct syncio_data *sd = td->io_ops->data; return sd->io_us[event]; } static int fio_vsyncio_append(struct thread_data *td, struct io_u *io_u) { struct syncio_data *sd = td->io_ops->data; if (ddir_sync(io_u->ddir)) return 0; if (io_u->offset == sd->last_offset && io_u->file == sd->last_file && io_u->ddir == sd->last_ddir) return 1; return 0; } static void fio_vsyncio_set_iov(struct syncio_data *sd, struct io_u *io_u, int idx) { sd->io_us[idx] = io_u; sd->iovecs[idx].iov_base = io_u->xfer_buf; sd->iovecs[idx].iov_len = io_u->xfer_buflen; sd->last_offset = io_u->offset + io_u->xfer_buflen; sd->last_file = io_u->file; sd->last_ddir = io_u->ddir; sd->queued_bytes += io_u->xfer_buflen; sd->queued++; } static int fio_vsyncio_queue(struct thread_data *td, struct io_u *io_u) { struct syncio_data *sd = td->io_ops->data; fio_ro_check(td, io_u); if (!fio_vsyncio_append(td, io_u)) { dprint(FD_IO, "vsyncio_queue: no append (%d)\n", sd->queued); /* * If we can't append and have stuff queued, tell fio to * commit those first and then retry this io */ if (sd->queued) return FIO_Q_BUSY; if (ddir_sync(io_u->ddir)) { int ret = do_io_u_sync(td, io_u); return fio_io_end(td, io_u, ret); } sd->queued = 0; sd->queued_bytes = 0; fio_vsyncio_set_iov(sd, io_u, 0); } else { if (sd->queued == td->o.iodepth) { dprint(FD_IO, "vsyncio_queue: max depth %d\n", sd->queued); return FIO_Q_BUSY; } dprint(FD_IO, "vsyncio_queue: append\n"); fio_vsyncio_set_iov(sd, io_u, sd->queued); } dprint(FD_IO, "vsyncio_queue: depth now %d\n", sd->queued); return FIO_Q_QUEUED; } /* * Check that we transferred all bytes, or saw an error, etc */ static int fio_vsyncio_end(struct thread_data *td, ssize_t bytes) { struct syncio_data *sd = td->io_ops->data; struct io_u *io_u; unsigned int i; int err; /* * transferred everything, perfect */ if (bytes == sd->queued_bytes) return 0; err = errno; for (i = 0; i < sd->queued; i++) { io_u = sd->io_us[i]; if (bytes == -1) { io_u->error = err; } else { unsigned int this_io; this_io = bytes; if (this_io > io_u->xfer_buflen) this_io = io_u->xfer_buflen; io_u->resid = io_u->xfer_buflen - this_io; io_u->error = 0; bytes -= this_io; } } if (bytes == -1) { td_verror(td, err, "xfer vsync"); return -err; } return 0; } static int fio_vsyncio_commit(struct thread_data *td) { struct syncio_data *sd = td->io_ops->data; struct fio_file *f; ssize_t ret; if (!sd->queued) return 0; io_u_mark_submit(td, sd->queued); f = sd->last_file; if (lseek(f->fd, sd->io_us[0]->offset, SEEK_SET) == -1) { int err = -errno; td_verror(td, errno, "lseek"); return err; } if (sd->last_ddir == DDIR_READ) ret = readv(f->fd, sd->iovecs, sd->queued); else ret = writev(f->fd, sd->iovecs, sd->queued); dprint(FD_IO, "vsyncio_commit: %d\n", (int) ret); sd->events = sd->queued; sd->queued = 0; return fio_vsyncio_end(td, ret); } static int fio_vsyncio_init(struct thread_data *td) { struct syncio_data *sd; sd = malloc(sizeof(*sd)); memset(sd, 0, sizeof(*sd)); sd->last_offset = -1ULL; sd->iovecs = malloc(td->o.iodepth * sizeof(struct iovec)); sd->io_us = malloc(td->o.iodepth * sizeof(struct io_u *)); td->io_ops->data = sd; return 0; } static void fio_vsyncio_cleanup(struct thread_data *td) { struct syncio_data *sd = td->io_ops->data; free(sd->iovecs); free(sd->io_us); free(sd); } static struct ioengine_ops ioengine_rw = { .name = "sync", .version = FIO_IOOPS_VERSION, .prep = fio_syncio_prep, .queue = fio_syncio_queue, .open_file = generic_open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, .flags = FIO_SYNCIO, }; static struct ioengine_ops ioengine_prw = { .name = "psync", .version = FIO_IOOPS_VERSION, .queue = fio_psyncio_queue, .open_file = generic_open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, .flags = FIO_SYNCIO, }; static struct ioengine_ops ioengine_vrw = { .name = "vsync", .version = FIO_IOOPS_VERSION, .init = fio_vsyncio_init, .cleanup = fio_vsyncio_cleanup, .queue = fio_vsyncio_queue, .commit = fio_vsyncio_commit, .event = fio_vsyncio_event, .getevents = fio_vsyncio_getevents, .open_file = generic_open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, .flags = FIO_SYNCIO, }; #ifdef CONFIG_PWRITEV static struct ioengine_ops ioengine_pvrw = { .name = "pvsync", .version = FIO_IOOPS_VERSION, .init = fio_vsyncio_init, .cleanup = fio_vsyncio_cleanup, .queue = fio_pvsyncio_queue, .open_file = generic_open_file, .close_file = generic_close_file, .get_file_size = generic_get_file_size, .flags = FIO_SYNCIO, }; #endif static void fio_init fio_syncio_register(void) { register_ioengine(&ioengine_rw); register_ioengine(&ioengine_prw); register_ioengine(&ioengine_vrw); #ifdef CONFIG_PWRITEV register_ioengine(&ioengine_pvrw); #endif } static void fio_exit fio_syncio_unregister(void) { unregister_ioengine(&ioengine_rw); unregister_ioengine(&ioengine_prw); unregister_ioengine(&ioengine_vrw); #ifdef CONFIG_PWRITEV unregister_ioengine(&ioengine_pvrw); #endif } fio-2.1.3/engines/windowsaio.c000066400000000000000000000244331222032232000162430ustar00rootroot00000000000000/* * windowsaio engine * * IO engine using Windows IO Completion Ports. */ #include #include #include #include #include #include "../fio.h" typedef BOOL (WINAPI *CANCELIOEX)(HANDLE hFile, LPOVERLAPPED lpOverlapped); int geterrno_from_win_error (DWORD code, int deferrno); struct fio_overlapped { OVERLAPPED o; struct io_u *io_u; BOOL io_complete; }; struct windowsaio_data { struct io_u **aio_events; HANDLE iocp; HANDLE iothread; HANDLE iocomplete_event; BOOL iothread_running; }; struct thread_ctx { HANDLE iocp; struct windowsaio_data *wd; }; static BOOL timeout_expired(DWORD start_count, DWORD end_count); static int fio_windowsaio_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec *t); static struct io_u *fio_windowsaio_event(struct thread_data *td, int event); static int fio_windowsaio_queue(struct thread_data *td, struct io_u *io_u); static void fio_windowsaio_cleanup(struct thread_data *td); static DWORD WINAPI IoCompletionRoutine(LPVOID lpParameter); static int fio_windowsaio_init(struct thread_data *td); static int fio_windowsaio_open_file(struct thread_data *td, struct fio_file *f); static int fio_windowsaio_close_file(struct thread_data fio_unused *td, struct fio_file *f); static int fio_windowsaio_init(struct thread_data *td) { struct windowsaio_data *wd; int rc = 0; wd = calloc(1, sizeof(struct windowsaio_data)); if (wd == NULL) { log_err("windowsaio: failed to allocate memory for engine data\n"); rc = 1; } if (!rc) { wd->aio_events = malloc(td->o.iodepth * sizeof(struct io_u*)); if (wd->aio_events == NULL) { log_err("windowsaio: failed to allocate memory for aio events list\n"); rc = 1; } } if (!rc) { /* Create an auto-reset event */ wd->iocomplete_event = CreateEvent(NULL, FALSE, FALSE, NULL); if (wd->iocomplete_event == NULL) { log_err("windowsaio: failed to create io complete event handle\n"); rc = 1; } } if (rc) { if (wd != NULL) { if (wd->aio_events != NULL) free(wd->aio_events); free(wd); } } td->io_ops->data = wd; if (!rc) { struct thread_ctx *ctx; struct windowsaio_data *wd; HANDLE hFile; hFile = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (hFile == INVALID_HANDLE_VALUE) { log_err("windowsaio: failed to create io completion port\n"); rc = 1; } wd = td->io_ops->data; wd->iothread_running = TRUE; wd->iocp = hFile; if (!rc) ctx = malloc(sizeof(struct thread_ctx)); if (!rc && ctx == NULL) { log_err("windowsaio: failed to allocate memory for thread context structure\n"); CloseHandle(hFile); rc = 1; } if (!rc) { ctx->iocp = hFile; ctx->wd = wd; wd->iothread = CreateThread(NULL, 0, IoCompletionRoutine, ctx, 0, NULL); if (wd->iothread == NULL) log_err("windowsaio: failed to create io completion thread\n"); } if (rc || wd->iothread == NULL) rc = 1; } return rc; } static void fio_windowsaio_cleanup(struct thread_data *td) { struct windowsaio_data *wd; wd = td->io_ops->data; if (wd != NULL) { wd->iothread_running = FALSE; WaitForSingleObject(wd->iothread, INFINITE); CloseHandle(wd->iothread); CloseHandle(wd->iocomplete_event); free(wd->aio_events); free(wd); td->io_ops->data = NULL; } } static int fio_windowsaio_open_file(struct thread_data *td, struct fio_file *f) { int rc = 0; DWORD flags = FILE_FLAG_POSIX_SEMANTICS | FILE_FLAG_OVERLAPPED; DWORD sharemode = FILE_SHARE_READ | FILE_SHARE_WRITE; DWORD openmode = OPEN_ALWAYS; DWORD access; dprint(FD_FILE, "fd open %s\n", f->file_name); if (f->filetype == FIO_TYPE_PIPE) { log_err("windowsaio: pipes are not supported\n"); return 1; } if (!strcmp(f->file_name, "-")) { log_err("windowsaio: can't read/write to stdin/out\n"); return 1; } if (td->o.odirect) flags |= FILE_FLAG_NO_BUFFERING; if (td->o.sync_io) flags |= FILE_FLAG_WRITE_THROUGH; /* * Inform Windows whether we're going to be doing sequential or * random io so it can tune the Cache Manager */ if (td->o.td_ddir == TD_DDIR_READ || td->o.td_ddir == TD_DDIR_WRITE) flags |= FILE_FLAG_SEQUENTIAL_SCAN; else flags |= FILE_FLAG_RANDOM_ACCESS; if (!td_write(td) || read_only) access = GENERIC_READ; else access = (GENERIC_READ | GENERIC_WRITE); if (td->o.create_on_open) openmode = OPEN_ALWAYS; else openmode = OPEN_EXISTING; f->hFile = CreateFile(f->file_name, access, sharemode, NULL, openmode, flags, NULL); if (f->hFile == INVALID_HANDLE_VALUE) { log_err("windowsaio: failed to open file \"%s\"\n", f->file_name); rc = 1; } /* Only set up the completion port and thread if we're not just * querying the device size */ if (!rc && td->io_ops->data != NULL) { struct windowsaio_data *wd; wd = td->io_ops->data; if (CreateIoCompletionPort(f->hFile, wd->iocp, 0, 0) == NULL) { log_err("windowsaio: failed to create io completion port\n"); rc = 1; } } return rc; } static int fio_windowsaio_close_file(struct thread_data fio_unused *td, struct fio_file *f) { int rc = 0; dprint(FD_FILE, "fd close %s\n", f->file_name); if (f->hFile != INVALID_HANDLE_VALUE) { if (!CloseHandle(f->hFile)) { log_info("windowsaio: failed to close file handle for \"%s\"\n", f->file_name); rc = 1; } } f->hFile = INVALID_HANDLE_VALUE; return rc; } static BOOL timeout_expired(DWORD start_count, DWORD end_count) { BOOL expired = FALSE; DWORD current_time; current_time = GetTickCount(); if ((end_count > start_count) && current_time >= end_count) expired = TRUE; else if (current_time < start_count && current_time > end_count) expired = TRUE; return expired; } static struct io_u* fio_windowsaio_event(struct thread_data *td, int event) { struct windowsaio_data *wd = td->io_ops->data; return wd->aio_events[event]; } static int fio_windowsaio_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec *t) { struct windowsaio_data *wd = td->io_ops->data; unsigned int dequeued = 0; struct io_u *io_u; int i; struct fio_overlapped *fov; DWORD start_count = 0; DWORD end_count = 0; DWORD status; DWORD mswait = 250; if (t != NULL) { mswait = (t->tv_sec * 1000) + (t->tv_nsec / 1000000); start_count = GetTickCount(); end_count = start_count + (t->tv_sec * 1000) + (t->tv_nsec / 1000000); } do { io_u_qiter(&td->io_u_all, io_u, i) { if (!(io_u->flags & IO_U_F_FLIGHT)) continue; fov = (struct fio_overlapped*)io_u->engine_data; if (fov->io_complete) { fov->io_complete = FALSE; ResetEvent(fov->o.hEvent); wd->aio_events[dequeued] = io_u; dequeued++; } if (dequeued >= min) break; } if (dequeued < min) { status = WaitForSingleObject(wd->iocomplete_event, mswait); if (status != WAIT_OBJECT_0 && dequeued >= min) break; } if (dequeued >= min || (t != NULL && timeout_expired(start_count, end_count))) break; } while (1); return dequeued; } static int fio_windowsaio_queue(struct thread_data *td, struct io_u *io_u) { struct fio_overlapped *o = io_u->engine_data; LPOVERLAPPED lpOvl = &o->o; DWORD iobytes; BOOL success = FALSE; int rc = FIO_Q_COMPLETED; fio_ro_check(td, io_u); lpOvl->Internal = STATUS_PENDING; lpOvl->InternalHigh = 0; lpOvl->Offset = io_u->offset & 0xFFFFFFFF; lpOvl->OffsetHigh = io_u->offset >> 32; switch (io_u->ddir) { case DDIR_WRITE: success = WriteFile(io_u->file->hFile, io_u->xfer_buf, io_u->xfer_buflen, &iobytes, lpOvl); break; case DDIR_READ: success = ReadFile(io_u->file->hFile, io_u->xfer_buf, io_u->xfer_buflen, &iobytes, lpOvl); break; case DDIR_SYNC: case DDIR_DATASYNC: case DDIR_SYNC_FILE_RANGE: success = FlushFileBuffers(io_u->file->hFile); if (!success) { log_err("windowsaio: failed to flush file buffers\n"); io_u->error = win_to_posix_error(GetLastError()); } return FIO_Q_COMPLETED; break; case DDIR_TRIM: log_err("windowsaio: manual TRIM isn't supported on Windows\n"); io_u->error = 1; io_u->resid = io_u->xfer_buflen; return FIO_Q_COMPLETED; break; default: assert(0); break; } if (success || GetLastError() == ERROR_IO_PENDING) rc = FIO_Q_QUEUED; else { io_u->error = win_to_posix_error(GetLastError()); io_u->resid = io_u->xfer_buflen; } return rc; } /* Runs as a thread and waits for queued IO to complete */ static DWORD WINAPI IoCompletionRoutine(LPVOID lpParameter) { OVERLAPPED *ovl; struct fio_overlapped *fov; struct io_u *io_u; struct windowsaio_data *wd; struct thread_ctx *ctx; ULONG_PTR ulKey = 0; DWORD bytes; ctx = (struct thread_ctx*)lpParameter; wd = ctx->wd; do { if (!GetQueuedCompletionStatus(ctx->iocp, &bytes, &ulKey, &ovl, 250) && ovl == NULL) continue; fov = CONTAINING_RECORD(ovl, struct fio_overlapped, o); io_u = fov->io_u; if (ovl->Internal == ERROR_SUCCESS) { io_u->resid = io_u->xfer_buflen - ovl->InternalHigh; io_u->error = 0; } else { io_u->resid = io_u->xfer_buflen; io_u->error = win_to_posix_error(GetLastError()); } fov->io_complete = TRUE; SetEvent(wd->iocomplete_event); } while (ctx->wd->iothread_running); CloseHandle(ctx->iocp); free(ctx); return 0; } static void fio_windowsaio_io_u_free(struct thread_data *td, struct io_u *io_u) { struct fio_overlapped *o = io_u->engine_data; if (o) { CloseHandle(o->o.hEvent); io_u->engine_data = NULL; free(o); } } static int fio_windowsaio_io_u_init(struct thread_data *td, struct io_u *io_u) { struct fio_overlapped *o; o = malloc(sizeof(*o)); o->io_complete = FALSE; o->io_u = io_u; o->o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (o->o.hEvent == NULL) { log_err("windowsaio: failed to create event handle\n"); free(o); return 1; } io_u->engine_data = o; return 0; } static struct ioengine_ops ioengine = { .name = "windowsaio", .version = FIO_IOOPS_VERSION, .init = fio_windowsaio_init, .queue = fio_windowsaio_queue, .getevents = fio_windowsaio_getevents, .event = fio_windowsaio_event, .cleanup = fio_windowsaio_cleanup, .open_file = fio_windowsaio_open_file, .close_file = fio_windowsaio_close_file, .get_file_size = generic_get_file_size, .io_u_init = fio_windowsaio_io_u_init, .io_u_free = fio_windowsaio_io_u_free, }; static void fio_init fio_windowsaio_register(void) { register_ioengine(&ioengine); } static void fio_exit fio_windowsaio_unregister(void) { unregister_ioengine(&ioengine); } fio-2.1.3/eta.c000066400000000000000000000315211222032232000131750ustar00rootroot00000000000000/* * Status and ETA code */ #include #include #include #include "fio.h" static char run_str[REAL_MAX_JOBS + 1]; /* * Sets the status of the 'td' in the printed status map. */ static void check_str_update(struct thread_data *td) { char c = run_str[td->thread_number - 1]; switch (td->runstate) { case TD_REAPED: if (td->error) c = 'X'; else if (td->sig) c = 'K'; else c = '_'; break; case TD_EXITED: c = 'E'; break; case TD_RAMP: c = '/'; break; case TD_RUNNING: if (td_rw(td)) { if (td_random(td)) { if (td->o.rwmix[DDIR_READ] == 100) c = 'r'; else if (td->o.rwmix[DDIR_WRITE] == 100) c = 'w'; else c = 'm'; } else { if (td->o.rwmix[DDIR_READ] == 100) c = 'R'; else if (td->o.rwmix[DDIR_WRITE] == 100) c = 'W'; else c = 'M'; } } else if (td_read(td)) { if (td_random(td)) c = 'r'; else c = 'R'; } else if (td_write(td)) { if (td_random(td)) c = 'w'; else c = 'W'; } else { if (td_random(td)) c = 'd'; else c = 'D'; } break; case TD_PRE_READING: c = 'p'; break; case TD_VERIFYING: c = 'V'; break; case TD_FSYNCING: c = 'F'; break; case TD_CREATED: c = 'C'; break; case TD_INITIALIZED: case TD_SETTING_UP: c = 'I'; break; case TD_NOT_CREATED: c = 'P'; break; default: log_err("state %d\n", td->runstate); } run_str[td->thread_number - 1] = c; } /* * Convert seconds to a printable string. */ void eta_to_str(char *str, unsigned long eta_sec) { unsigned int d, h, m, s; int disp_hour = 0; s = eta_sec % 60; eta_sec /= 60; m = eta_sec % 60; eta_sec /= 60; h = eta_sec % 24; eta_sec /= 24; d = eta_sec; if (d) { disp_hour = 1; str += sprintf(str, "%02ud:", d); } if (h || disp_hour) str += sprintf(str, "%02uh:", h); str += sprintf(str, "%02um:", m); str += sprintf(str, "%02us", s); } /* * Best effort calculation of the estimated pending runtime of a job. */ static int thread_eta(struct thread_data *td) { unsigned long long bytes_total, bytes_done; unsigned long eta_sec = 0; unsigned long elapsed; elapsed = (mtime_since_now(&td->epoch) + 999) / 1000; bytes_total = td->total_io_size; if (td->o.fill_device && td->o.size == -1ULL) { if (!td->fill_device_size || td->fill_device_size == -1ULL) return 0; bytes_total = td->fill_device_size; } if (td->o.zone_size && td->o.zone_skip && bytes_total) { unsigned int nr_zones; uint64_t zone_bytes; zone_bytes = bytes_total + td->o.zone_size + td->o.zone_skip; nr_zones = (zone_bytes - 1) / (td->o.zone_size + td->o.zone_skip); bytes_total -= nr_zones * td->o.zone_skip; } /* * if writing and verifying afterwards, bytes_total will be twice the * size. In a mixed workload, verify phase will be the size of the * first stage writes. */ if (td->o.do_verify && td->o.verify && td_write(td)) { if (td_rw(td)) { unsigned int perc = 50; if (td->o.rwmix[DDIR_WRITE]) perc = td->o.rwmix[DDIR_WRITE]; bytes_total += (bytes_total * perc) / 100; } else bytes_total <<= 1; } if (td->runstate == TD_RUNNING || td->runstate == TD_VERIFYING) { double perc, perc_t; bytes_done = ddir_rw_sum(td->io_bytes); perc = (double) bytes_done / (double) bytes_total; if (perc > 1.0) perc = 1.0; if (td->o.time_based) { perc_t = (double) elapsed / (double) td->o.timeout; if (perc_t < perc) perc = perc_t; } eta_sec = (unsigned long) (elapsed * (1.0 / perc)) - elapsed; if (td->o.timeout && eta_sec > (td->o.timeout + done_secs - elapsed)) eta_sec = td->o.timeout + done_secs - elapsed; } else if (td->runstate == TD_NOT_CREATED || td->runstate == TD_CREATED || td->runstate == TD_INITIALIZED || td->runstate == TD_SETTING_UP || td->runstate == TD_RAMP || td->runstate == TD_PRE_READING) { int t_eta = 0, r_eta = 0; unsigned long long rate_bytes; /* * We can only guess - assume it'll run the full timeout * if given, otherwise assume it'll run at the specified rate. */ if (td->o.timeout) { t_eta = td->o.timeout + td->o.start_delay + td->o.ramp_time; if (in_ramp_time(td)) { unsigned long ramp_left; ramp_left = mtime_since_now(&td->epoch); ramp_left = (ramp_left + 999) / 1000; if (ramp_left <= t_eta) t_eta -= ramp_left; } } rate_bytes = ddir_rw_sum(td->o.rate); if (rate_bytes) { r_eta = (bytes_total / 1024) / rate_bytes; r_eta += td->o.start_delay; } if (r_eta && t_eta) eta_sec = min(r_eta, t_eta); else if (r_eta) eta_sec = r_eta; else if (t_eta) eta_sec = t_eta; else eta_sec = 0; } else { /* * thread is already done or waiting for fsync */ eta_sec = 0; } return eta_sec; } static void calc_rate(int unified_rw_rep, unsigned long mtime, unsigned long long *io_bytes, unsigned long long *prev_io_bytes, unsigned int *rate) { int i; for (i = 0; i < DDIR_RWDIR_CNT; i++) { unsigned long long diff; diff = io_bytes[i] - prev_io_bytes[i]; if (unified_rw_rep) { rate[i] = 0; rate[0] += ((1000 * diff) / mtime) / 1024; } else rate[i] = ((1000 * diff) / mtime) / 1024; prev_io_bytes[i] = io_bytes[i]; } } static void calc_iops(int unified_rw_rep, unsigned long mtime, unsigned long long *io_iops, unsigned long long *prev_io_iops, unsigned int *iops) { int i; for (i = 0; i < DDIR_RWDIR_CNT; i++) { unsigned long long diff; diff = io_iops[i] - prev_io_iops[i]; if (unified_rw_rep) { iops[i] = 0; iops[0] += (diff * 1000) / mtime; } else iops[i] = (diff * 1000) / mtime; prev_io_iops[i] = io_iops[i]; } } /* * Print status of the jobs we know about. This includes rate estimates, * ETA, thread state, etc. */ int calc_thread_status(struct jobs_eta *je, int force) { struct thread_data *td; int i, unified_rw_rep; unsigned long rate_time, disp_time, bw_avg_time, *eta_secs; unsigned long long io_bytes[DDIR_RWDIR_CNT]; unsigned long long io_iops[DDIR_RWDIR_CNT]; struct timeval now; static unsigned long long rate_io_bytes[DDIR_RWDIR_CNT]; static unsigned long long disp_io_bytes[DDIR_RWDIR_CNT]; static unsigned long long disp_io_iops[DDIR_RWDIR_CNT]; static struct timeval rate_prev_time, disp_prev_time; if (!force) { if (output_format != FIO_OUTPUT_NORMAL && f_out == stdout) return 0; if (temp_stall_ts || eta_print == FIO_ETA_NEVER) return 0; if (!isatty(STDOUT_FILENO) && (eta_print != FIO_ETA_ALWAYS)) return 0; } if (!ddir_rw_sum(rate_io_bytes)) fill_start_time(&rate_prev_time); if (!ddir_rw_sum(disp_io_bytes)) fill_start_time(&disp_prev_time); eta_secs = malloc(thread_number * sizeof(unsigned long)); memset(eta_secs, 0, thread_number * sizeof(unsigned long)); je->elapsed_sec = (mtime_since_genesis() + 999) / 1000; io_bytes[DDIR_READ] = io_bytes[DDIR_WRITE] = io_bytes[DDIR_TRIM] = 0; io_iops[DDIR_READ] = io_iops[DDIR_WRITE] = io_iops[DDIR_TRIM] = 0; bw_avg_time = ULONG_MAX; unified_rw_rep = 0; for_each_td(td, i) { unified_rw_rep += td->o.unified_rw_rep; if (is_power_of_2(td->o.kb_base)) je->is_pow2 = 1; je->unit_base = td->o.unit_base; if (td->o.bw_avg_time < bw_avg_time) bw_avg_time = td->o.bw_avg_time; if (td->runstate == TD_RUNNING || td->runstate == TD_VERIFYING || td->runstate == TD_FSYNCING || td->runstate == TD_PRE_READING) { je->nr_running++; if (td_read(td)) { je->t_rate[0] += td->o.rate[DDIR_READ]; je->t_iops[0] += td->o.rate_iops[DDIR_READ]; je->m_rate[0] += td->o.ratemin[DDIR_READ]; je->m_iops[0] += td->o.rate_iops_min[DDIR_READ]; } if (td_write(td)) { je->t_rate[1] += td->o.rate[DDIR_WRITE]; je->t_iops[1] += td->o.rate_iops[DDIR_WRITE]; je->m_rate[1] += td->o.ratemin[DDIR_WRITE]; je->m_iops[1] += td->o.rate_iops_min[DDIR_WRITE]; } if (td_trim(td)) { je->t_rate[2] += td->o.rate[DDIR_TRIM]; je->t_iops[2] += td->o.rate_iops[DDIR_TRIM]; je->m_rate[2] += td->o.ratemin[DDIR_TRIM]; je->m_iops[2] += td->o.rate_iops_min[DDIR_TRIM]; } je->files_open += td->nr_open_files; } else if (td->runstate == TD_RAMP) { je->nr_running++; je->nr_ramp++; } else if (td->runstate == TD_SETTING_UP) { je->nr_running++; je->nr_setting_up++; } else if (td->runstate < TD_RUNNING) je->nr_pending++; if (je->elapsed_sec >= 3) eta_secs[i] = thread_eta(td); else eta_secs[i] = INT_MAX; check_str_update(td); if (td->runstate > TD_SETTING_UP) { int ddir; for (ddir = DDIR_READ; ddir < DDIR_RWDIR_CNT; ddir++) { if (unified_rw_rep) { io_bytes[0] += td->io_bytes[ddir]; io_iops[0] += td->io_blocks[ddir]; } else { io_bytes[ddir] += td->io_bytes[ddir]; io_iops[ddir] += td->io_blocks[ddir]; } } } } if (exitall_on_terminate) je->eta_sec = INT_MAX; else je->eta_sec = 0; for_each_td(td, i) { if (exitall_on_terminate) { if (eta_secs[i] < je->eta_sec) je->eta_sec = eta_secs[i]; } else { if (eta_secs[i] > je->eta_sec) je->eta_sec = eta_secs[i]; } } free(eta_secs); fio_gettime(&now, NULL); rate_time = mtime_since(&rate_prev_time, &now); if (write_bw_log && rate_time > bw_avg_time && !in_ramp_time(td)) { calc_rate(unified_rw_rep, rate_time, io_bytes, rate_io_bytes, je->rate); memcpy(&rate_prev_time, &now, sizeof(now)); add_agg_sample(je->rate[DDIR_READ], DDIR_READ, 0); add_agg_sample(je->rate[DDIR_WRITE], DDIR_WRITE, 0); add_agg_sample(je->rate[DDIR_TRIM], DDIR_TRIM, 0); } disp_time = mtime_since(&disp_prev_time, &now); /* * Allow a little slack, the target is to print it every 1000 msecs */ if (!force && disp_time < 900) return 0; calc_rate(unified_rw_rep, disp_time, io_bytes, disp_io_bytes, je->rate); calc_iops(unified_rw_rep, disp_time, io_iops, disp_io_iops, je->iops); memcpy(&disp_prev_time, &now, sizeof(now)); if (!force && !je->nr_running && !je->nr_pending) return 0; je->nr_threads = thread_number; memcpy(je->run_str, run_str, thread_number * sizeof(char)); return 1; } void display_thread_status(struct jobs_eta *je) { static struct timeval disp_eta_new_line; static int eta_new_line_init, eta_new_line_pending; static int linelen_last; static int eta_good; char output[REAL_MAX_JOBS + 512], *p = output; char eta_str[128]; double perc = 0.0; if (je->eta_sec != INT_MAX && je->elapsed_sec) { perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec); eta_to_str(eta_str, je->eta_sec); } if (eta_new_line_pending) { eta_new_line_pending = 0; p += sprintf(p, "\n"); } p += sprintf(p, "Jobs: %d (f=%d)", je->nr_running, je->files_open); if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) { char *tr, *mr; mr = num2str(je->m_rate[0] + je->m_rate[1], 4, 0, je->is_pow2, 8); tr = num2str(je->t_rate[0] + je->t_rate[1], 4, 0, je->is_pow2, 8); p += sprintf(p, ", CR=%s/%s KB/s", tr, mr); free(tr); free(mr); } else if (je->m_iops[0] || je->m_iops[1] || je->t_iops[0] || je->t_iops[1]) { p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops[0] + je->t_iops[1], je->m_iops[0] + je->m_iops[1]); } if (je->eta_sec != INT_MAX && je->nr_running) { char perc_str[32]; char *iops_str[DDIR_RWDIR_CNT]; char *rate_str[DDIR_RWDIR_CNT]; size_t left; int l; int ddir; if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running) strcpy(perc_str, "-.-% done"); else { double mult = 100.0; if (je->nr_setting_up && je->nr_running) mult *= (1.0 - (double) je->nr_setting_up / (double) je->nr_running); eta_good = 1; perc *= mult; sprintf(perc_str, "%3.1f%% done", perc); } for (ddir = DDIR_READ; ddir < DDIR_RWDIR_CNT; ddir++) { rate_str[ddir] = num2str(je->rate[ddir], 5, 1024, je->is_pow2, je->unit_base); iops_str[ddir] = num2str(je->iops[ddir], 4, 1, 0, 0); } left = sizeof(output) - (p - output) - 1; l = snprintf(p, left, ": [%s] [%s] [%s/%s/%s /s] [%s/%s/%s iops] [eta %s]", je->run_str, perc_str, rate_str[DDIR_READ], rate_str[DDIR_WRITE], rate_str[DDIR_TRIM], iops_str[DDIR_READ], iops_str[DDIR_WRITE], iops_str[DDIR_TRIM], eta_str); p += l; if (l >= 0 && l < linelen_last) p += sprintf(p, "%*s", linelen_last - l, ""); linelen_last = l; for (ddir = DDIR_READ; ddir < DDIR_RWDIR_CNT; ddir++) { free(rate_str[ddir]); free(iops_str[ddir]); } } p += sprintf(p, "\r"); printf("%s", output); if (!eta_new_line_init) { fio_gettime(&disp_eta_new_line, NULL); eta_new_line_init = 1; } else if (eta_new_line && mtime_since_now(&disp_eta_new_line) > eta_new_line * 1000) { fio_gettime(&disp_eta_new_line, NULL); eta_new_line_pending = 1; } fflush(stdout); } void print_thread_status(void) { struct jobs_eta *je; size_t size; if (!thread_number) return; size = sizeof(*je) + thread_number * sizeof(char) + 1; je = malloc(size); memset(je, 0, size); if (calc_thread_status(je, 0)) display_thread_status(je); free(je); } void print_status_init(int thr_number) { run_str[thr_number] = 'P'; } fio-2.1.3/examples/000077500000000000000000000000001222032232000140745ustar00rootroot00000000000000fio-2.1.3/examples/1mbs_clients.fio000066400000000000000000000015571222032232000171660ustar00rootroot00000000000000; Keep adding 1024kb/s reading clients at 4 seconds [global] size=32m rw=read directory=tmp rate=1250 ratemin=1024 [file1] startdelay=0 [file2] startdelay=4 [file3] startdelay=8 [file4] startdelay=12 [file5] startdelay=16 [file6] startdelay=20 [file7] startdelay=24 [file8] startdelay=28 [file9] startdelay=32 [file10] startdelay=36 [file11] startdelay=40 [file12] startdelay=44 [file13] startdelay=48 [file14] startdelay=52 [file15] startdelay=56 [file16] startdelay=60 [file17] startdelay=64 [file18] startdelay=68 [file19] startdelay=72 [file20] startdelay=76 [file21] startdelay=80 [file22] startdelay=84 [file23] startdelay=88 [file24] startdelay=92 [file25] startdelay=96 [file26] startdelay=100 [file27] startdelay=104 [file28] startdelay=108 [file29] startdelay=112 [file30] startdelay=116 [file31] startdelay=120 [file32] startdelay=124 fio-2.1.3/examples/aio-read.fio000066400000000000000000000003151222032232000162530ustar00rootroot00000000000000; Read 4 files with aio at different depths [global] ioengine=libaio buffered=0 rw=randread bs=128k size=512m directory=/data1 [file1] iodepth=4 [file2] iodepth=32 [file3] iodepth=8 [file4] iodepth=16 fio-2.1.3/examples/cpuio.fio000066400000000000000000000001131222032232000157050ustar00rootroot00000000000000[global] ioengine=cpuio time_based runtime=10 [burn50percent] cpuload=50 fio-2.1.3/examples/disk-zone-profile.fio000066400000000000000000000003561222032232000201400ustar00rootroot00000000000000; Read disk in zones of 128m/2g, generating a plot of that afterwards ; should give a nice picture of the zoning of this drive [global] bs=64k direct=1 rw=read ioengine=libaio iodepth=2 zonesize=256m zoneskip=2g write_bw_log [/dev/sdb] fio-2.1.3/examples/e4defrag.fio000066400000000000000000000010171222032232000162530ustar00rootroot00000000000000[global] ioengine=e4defrag directory=/scratch nrfiles=1 filesize=100M size=100M bs=32k #group_reporting [isolated-e4defrag] # It is important to disable buffered io buffered=0 donorname=file.def filename=file1 inplace=0 rw=write # Run e4defrag and aio-dio workers in parallel [e4defrag] stonewall time_based=30 runtime=30 ioengine=e4defrag buffered=0 donorname=file.def filename=file1 inplace=0 rw=write [random-aio-32k] ioengine=libaio runtime=30 verify=md5 direct=1 bs=64k iodepth=128 filename=file1 rw=randrw numjobs=4 fio-2.1.3/examples/e4defrag2.fio000066400000000000000000000025021222032232000163350ustar00rootroot00000000000000################################################# # Hardcode defragmentation patterns # Please be carefull, it can trigger kernel panic ################################################# [global] ioengine=e4defrag group_reporting directory=/scratch nrfiles=1 filesize=100M size=100M donorname=file.def bs=32k ########### # Run several defragmentation threads for different files, but # use shared donor file [parallel-e4defrag] buffered=0 inplace=0 rw=write numjobs=4 ######## # Run two defragmentation threads, each thread use another's file # as donor file [e4defrag-1] stonewall inplace=0 rw=write donorname=e4defrag-2 [e4defrag-2] inplace=0 rw=write donorname=e4defrag-1 ########### # Run random defragment activity [e4defrag-fuzzer-4k] stonewall inplace=1 bs=4k rw=randwrite filename=file donorname=file.def ######## # Run random e4defrag and various aio workers in parallel [e4defrag-fuzzer-4k] stonewall continue_on_error=all inplace=1 bs=4k donorname=file3.def filename=file3 time_based=30 rw=randwrite [buffered-aio-32k] continue_on_error=none verify=md5 buffered=1 ioengine=libaio iodepth=128 bs=32k filename=file3 rw=randrw runtime=30 time_based=30 numjobs=4 [direct-aio-32k] continue_on_error=none verify=md5 buffered=0 direct=1 ioengine=libaio iodepth=128 bs=32k filename=file3 rw=randrw runtime=30 time_based=30 numjobs=4 fio-2.1.3/examples/enospc-pressure.fio000066400000000000000000000020331222032232000177260ustar00rootroot00000000000000# # Test for race-condition DIO-write vs punch_hole # If race exist dio may rewrite punched block after # it was allocated to another file, we will catch that # by verifying blocks content # [global] ioengine=libaio directory=/scratch # File size is reasonably huge to provoke ENOSPC filesize=128G size=999G iodepth=128 # Expect write failure due to ENOSPC, skip error dump continue_on_error=write ignore_error=,ENOSPC error_dump=0 fallocate=none exitall # Two threads (dio and punch_hole) operate on single file:'raicer', # We do not care about data content here [dio-raicer] bs=128k direct=1 buffered=0 rw=randwrite runtime=100 filename=raicer time_based [punch_hole-raicer] bs=4k rw=randtrim filename=raicer # Verifier thread continiously write to newly allcated blocks # and veryfy written content [aio-dio-verifier] create_on_open=1 verify=crc32c-intel verify_fatal=1 verify_dump=1 verify_backlog=1024 verify_async=4 direct=1 # block size should be equals to fs block size to prevent short writes bs=4k rw=randrw filename=aio-dio-verifier fio-2.1.3/examples/falloc.fio000066400000000000000000000013721222032232000160360ustar00rootroot00000000000000[global] ioengine=falloc iodepth=1 direct=0 buffered=0 directory=/scratch nrfiles=1 size=100M filesize=100M group_reporting # Run falloc and punch_hole threads in parallel # After activity file will be highly fragmented [falloc-fuzzer] stonewall runtime=10 time_based=10 bssplit=4k/10:64k/50:32k/40 rw=randwrite numjobs=1 filename=fragmented_file [punch hole-fuzzer] bs=4k runtime=10 time_based=10 rw=randtrim numjobs=2 filename=fragmented_file ## Mesure IO performance on fragmented file [sequential aio-dio write] stonewall ioengine=libaio numjobs=1 iodepth=128 buffered=0 direct=1 rw=write bs=64k filename=fragmented_file [sequential buffered read] stonewall ioengine=sync numjobs=1 iodepth=1 buffered=1 direct=0 rw=read bs=64k filename=fragmented_file fio-2.1.3/examples/flow.fio000066400000000000000000000005111222032232000155370ustar00rootroot00000000000000# Example usage of flows. The below will have roughly a 1:8 difference # between job2 and job1. [global] norandommap thread time_based runtime=30 direct=1 ioengine=libaio iodepth=256 size=100g bs=8k filename=/tmp/testfile flow_watermark=100 flow_sleep=1000 [job2] numjobs=1 rw=write flow=-8 [job1] numjobs=1 rw=randread flow=1 fio-2.1.3/examples/fsx.fio000066400000000000000000000003211222032232000153670ustar00rootroot00000000000000; This job file works pretty works similarly to running fsx-linux ; with -r 4096 -w 4096 -Z -N 500000 [file] ioengine=libaio iodepth=1 rw=randrw size=256k bs=4k norandommap direct=1 loops=500000 rwmixcycle=40 fio-2.1.3/examples/fusion-aw-sync.fio000066400000000000000000000005551222032232000174620ustar00rootroot00000000000000# Example Job File that randomly writes 8k worth of data atomically for # 60 seconds. [rw_aw_file_sync] rw=randwrite ioengine=fusion-aw-sync blocksize=8k blockalign=8k # if file system supports atomic write filename=/mnt/fs/file # or test on a direct block device instead #filename=/dev/fioa randrepeat=1 fallocate=none direct=1 invalidate=0 runtime=60 time_based fio-2.1.3/examples/iometer-file-access-server.fio000066400000000000000000000006671222032232000217300ustar00rootroot00000000000000# This job file tries to mimic the Intel IOMeter File Server Access Pattern [global] description=Emulation of Intel IOmeter File Server Access Pattern [iometer] bssplit=512/10:1k/5:2k/5:4k/60:8k/2:16k/4:32k/4:64k/10 rw=randrw rwmixread=80 direct=1 size=4g ioengine=libaio # IOMeter defines the server loads as the following: # iodepth=1 Linear # iodepth=4 Very Light # iodepth=8 Light # iodepth=64 Moderate # iodepth=256 Heavy iodepth=64 fio-2.1.3/examples/netio.fio000066400000000000000000000006131222032232000157110ustar00rootroot00000000000000# Example network job, just defines two clients that send/recv data [global] ioengine=net #Use hostname=/tmp.fio.sock for local unix domain sockets port=8888 #Use =udp for UDP, =unix for local unix domain socket protocol=tcp bs=4k size=100g #set the below option to enable end-to-end data integrity tests #verify=md5 [receiver] listen rw=read [sender] hostname=localhost startdelay=1 rw=write fio-2.1.3/examples/netio_multicast.fio000066400000000000000000000005771222032232000200070ustar00rootroot00000000000000# netio UDP multicast example. Writers and readers can be run on separate hosts. [global] ioengine=net protocol=udp bs=64 size=100m # Set interface IP to send/receive traffic through specific network interface #interface=10.8.16.22 port=10000 hostname=239.0.0.0 ttl=1 [pingpong_reader] pingpong=1 rw=read [normal_reader] rw=read [pingpong_writer] startdelay=1 pingpong=1 rw=write fio-2.1.3/examples/null.fio000066400000000000000000000001421222032232000155420ustar00rootroot00000000000000[global] bs=4k gtod_reduce=1 [null] ioengine=null size=100g rw=randread norandommap time_based=0 fio-2.1.3/examples/numa.fio000066400000000000000000000006211222032232000155320ustar00rootroot00000000000000; setup numa policy for each thread ; 'numactl --show' to determine the maximum numa nodes [global] ioengine=libaio buffered=0 rw=randread bs=512K iodepth=16 size=512m filename=/dev/sdb1 ; Fix memory blocks (512K * 16) in numa node 0 [job1] numa_cpu_nodes=0 numa_mem_policy=bind:0 ; Interleave memory blocks (512K * 16) in numa node 0 and 1 [job2] numa_cpu_nodes=0-1 numa_mem_policy=interleave:0-1 fio-2.1.3/examples/rdmaio-client.fio000066400000000000000000000002541222032232000173230ustar00rootroot00000000000000# Example rdma client job [global] ioengine=rdma filename=[ip_addr]/[port]/[RDMA_WRITE/RDMA_READ/SEND] bs=1m size=100g [sender] rw=write iodepth=1 iodepth_batch_complete=1fio-2.1.3/examples/rdmaio-server.fio000066400000000000000000000001711222032232000173510ustar00rootroot00000000000000# Example rdma server job [global] ioengine=rdma filename=[ip_addr]/[port] bs=1m size=100g [receiver] rw=read iodepth=16fio-2.1.3/examples/ssd-steadystate.fio000066400000000000000000000022231222032232000177130ustar00rootroot00000000000000# Get a decent idea about the steady state performance of an SSD. # # First we sequentially write the drive. Then we completely # overwrite the device again, this time randomly at 4K. The former gives # us a good idea of the ideal write performance, you should see flat graph # of steady write performance. The latter we would expect to start out at # approximately the same rate as the sequential fill, but at some point # hit a write cliff and hit steady state. The latency numbers of the steady # state also provide a good idea of what kind of latencies to expect when # the device is pushed to steady state instead of peak benchmark-like # numbers that are usually reported. # # Note that this is a DESTRUCTIVE test. It operates on the device itself. # It's not destructive in the sense that it will ruin the device, but # whatever data you have on there will be gone. # [global] ioengine=libaio direct=1 group_reporting filename=/dev/fioa [sequential-fill] description=Sequential fill phase rw=write iodepth=16 bs=1M [random-write-steady] stonewall description=Random write steady state phase rw=randwrite bs=4K iodepth=32 numjobs=4 write_bw_log=fioa-steady-state fio-2.1.3/examples/ssd-test.fio000066400000000000000000000015161222032232000163440ustar00rootroot00000000000000# Do some important numbers on SSD drives, to gauge what kind of # performance you might get out of them. # # Sequential read and write speeds are tested, these are expected to be # high. Random reads should also be fast, random writes are where crap # drives are usually separated from the good drives. # # This uses a queue depth of 4. New SATA SSD's will support up to 32 # in flight commands, so it may also be interesting to increase the queue # depth and compare. Note that most real-life usage will not see that # large of a queue depth, so 4 is more representative of normal use. # [global] bs=4k ioengine=libaio iodepth=4 size=1g direct=1 runtime=60 directory=/mount-point-of-ssd filename=ssd.test.file [seq-read] rw=read stonewall [rand-read] rw=randread stonewall [seq-write] rw=write stonewall [rand-write] rw=randwrite stonewall fio-2.1.3/examples/surface-scan.fio000066400000000000000000000006171222032232000171510ustar00rootroot00000000000000; writes 512 byte verification blocks until the disk is full, ; then verifies written data [global] thread=1 bs=64k direct=1 ioengine=sync verify=meta verify_pattern=0xaa555aa5 verify_interval=512 [write-phase] filename=datafile.tmp ; or use a full disk, for example /dev/sda rw=write fill_device=1 do_verify=0 [verify-phase] stonewall create_serialize=0 filename=datafile.tmp rw=read do_verify=1 fio-2.1.3/examples/tiobench-example.fio000066400000000000000000000004371222032232000200230ustar00rootroot00000000000000; tiobench like setup, add more fX files between the stonewalls to ; create more threads [global] direct=1 size=512m bsrange=4k-4k timeout=60 numjobs=4 ; 4 simultaneous threads for each job [f1] rw=write [f2] stonewall rw=randwrite [f3] stonewall rw=read [f4] stonewall rw=randread fio-2.1.3/examples/zipf.fio000066400000000000000000000003351222032232000155440ustar00rootroot00000000000000# Example job file for using a zipf distribution instead # of a purely random workload where each block is read # or written once. [job] ioengine=null rw=randread norandommap size=1280m bs=4k random_distribution=zipf:0.5 fio-2.1.3/fifo.c000066400000000000000000000044051222032232000133500ustar00rootroot00000000000000/* * A simple kernel FIFO implementation. * * Copyright (C) 2004 Stelian Pop * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include #include #include "fifo.h" struct fifo *fifo_alloc(unsigned int size) { struct fifo *fifo; fifo = malloc(sizeof(struct fifo)); if (!fifo) return NULL; fifo->buffer = malloc(size); fifo->size = size; fifo->in = fifo->out = 0; return fifo; } void fifo_free(struct fifo *fifo) { free(fifo->buffer); free(fifo); } unsigned int fifo_put(struct fifo *fifo, void *buffer, unsigned int len) { unsigned int l; len = min(len, fifo_room(fifo)); /* first put the data starting from fifo->in to buffer end */ l = min(len, fifo->size - (fifo->in & (fifo->size - 1))); memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l); /* then put the rest (if any) at the beginning of the buffer */ memcpy(fifo->buffer, buffer + l, len - l); /* * Ensure that we add the bytes to the fifo -before- * we update the fifo->in index. */ fifo->in += len; return len; } unsigned int fifo_get(struct fifo *fifo, void *buf, unsigned int len) { len = min(len, fifo->in - fifo->out); if (buf) { unsigned int l; /* * first get the data from fifo->out until the end of the buffer */ l = min(len, fifo->size - (fifo->out & (fifo->size - 1))); memcpy(buf, fifo->buffer + (fifo->out & (fifo->size - 1)), l); /* * then get the rest (if any) from the beginning of the buffer */ memcpy(buf + l, fifo->buffer, len - l); } fifo->out += len; if (fifo->in == fifo->out) fifo->in = fifo->out = 0; return len; } fio-2.1.3/fifo.h000066400000000000000000000032671222032232000133620ustar00rootroot00000000000000/* * A simple FIFO implementation. * * Copyright (C) 2004 Stelian Pop * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ struct fifo { unsigned char *buffer; /* the buffer holding the data */ unsigned int size; /* the size of the allocated buffer */ unsigned int in; /* data is added at offset (in % size) */ unsigned int out; /* data is extracted from off. (out % size) */ }; struct fifo *fifo_alloc(unsigned int); unsigned int fifo_put(struct fifo *, void *, unsigned int); unsigned int fifo_get(struct fifo *, void *, unsigned int); void fifo_free(struct fifo *); static inline unsigned int fifo_len(struct fifo *fifo) { return fifo->in - fifo->out; } static inline unsigned int fifo_room(struct fifo *fifo) { return fifo->size - fifo->in + fifo->out; } #ifndef min #define min(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x < _y ? _x : _y; }) #endif #ifndef max #define max(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x > _y ? _x : _y; }) #endif fio-2.1.3/file.h000066400000000000000000000110541222032232000133470ustar00rootroot00000000000000#ifndef FIO_FILE_H #define FIO_FILE_H #include #include "compiler/compiler.h" #include "io_ddir.h" #include "flist.h" #include "lib/zipf.h" #include "lib/axmap.h" #include "lib/lfsr.h" /* * The type of object we are working on */ enum fio_filetype { FIO_TYPE_FILE = 1, /* plain file */ FIO_TYPE_BD, /* block device */ FIO_TYPE_CHAR, /* character device */ FIO_TYPE_PIPE, /* pipe */ }; enum fio_file_flags { FIO_FILE_open = 1 << 0, /* file is open */ FIO_FILE_closing = 1 << 1, /* file being closed */ FIO_FILE_extend = 1 << 2, /* needs extend */ FIO_FILE_done = 1 << 3, /* io completed to this file */ FIO_FILE_size_known = 1 << 4, /* size has been set */ FIO_FILE_hashed = 1 << 5, /* file is on hash */ FIO_FILE_partial_mmap = 1 << 6, /* can't do full mmap */ }; enum file_lock_mode { FILE_LOCK_NONE, FILE_LOCK_EXCLUSIVE, FILE_LOCK_READWRITE, }; /* * roundrobin available files, or choose one at random, or do each one * serially. */ enum { FIO_FSERVICE_RANDOM = 1, FIO_FSERVICE_RR = 2, FIO_FSERVICE_SEQ = 3, }; /* * No pre-allocation when laying down files, or call posix_fallocate(), or * call fallocate() with FALLOC_FL_KEEP_SIZE set. */ enum fio_fallocate_mode { FIO_FALLOCATE_NONE = 1, FIO_FALLOCATE_POSIX = 2, FIO_FALLOCATE_KEEP_SIZE = 3, }; /* * Each thread_data structure has a number of files associated with it, * this structure holds state information for a single file. */ struct fio_file { struct flist_head hash_list; enum fio_filetype filetype; int fd; int shadow_fd; #ifdef WIN32 HANDLE hFile; HANDLE ioCP; #endif /* * filename and possible memory mapping */ char *file_name; unsigned int major, minor; int fileno; void *mmap_ptr; size_t mmap_sz; off_t mmap_off; /* * size of the file, offset into file, and io size from that offset */ uint64_t real_file_size; uint64_t file_offset; uint64_t io_size; uint64_t last_pos; uint64_t last_start; uint64_t first_write; uint64_t last_write; /* * For use by the io engine */ uint64_t engine_data; /* * if io is protected by a semaphore, this is set */ union { struct fio_mutex *lock; struct fio_rwlock *rwlock; }; /* * block map for random io */ struct axmap *io_axmap; struct fio_lfsr lfsr; /* * Used for zipf random distribution */ struct zipf_state zipf; int references; enum fio_file_flags flags; struct disk_util *du; }; #define FILE_FLAG_FNS(name) \ static inline void fio_file_set_##name(struct fio_file *f) \ { \ (f)->flags |= FIO_FILE_##name; \ } \ static inline void fio_file_clear_##name(struct fio_file *f) \ { \ (f)->flags &= ~FIO_FILE_##name; \ } \ static inline int fio_file_##name(struct fio_file *f) \ { \ return ((f)->flags & FIO_FILE_##name) != 0; \ } FILE_FLAG_FNS(open); FILE_FLAG_FNS(closing); FILE_FLAG_FNS(extend); FILE_FLAG_FNS(done); FILE_FLAG_FNS(size_known); FILE_FLAG_FNS(hashed); FILE_FLAG_FNS(partial_mmap); #undef FILE_FLAG_FNS /* * File setup/shutdown */ struct thread_data; extern void close_files(struct thread_data *); extern void close_and_free_files(struct thread_data *); extern uint64_t get_start_offset(struct thread_data *); extern int __must_check setup_files(struct thread_data *); extern int __must_check file_invalidate_cache(struct thread_data *, struct fio_file *); extern int __must_check generic_open_file(struct thread_data *, struct fio_file *); extern int __must_check generic_close_file(struct thread_data *, struct fio_file *); extern int __must_check generic_get_file_size(struct thread_data *, struct fio_file *); extern int __must_check file_lookup_open(struct fio_file *f, int flags); extern int __must_check pre_read_files(struct thread_data *); extern int add_file(struct thread_data *, const char *); extern int add_file_exclusive(struct thread_data *, const char *); extern void get_file(struct fio_file *); extern int __must_check put_file(struct thread_data *, struct fio_file *); extern void put_file_log(struct thread_data *, struct fio_file *); extern void lock_file(struct thread_data *, struct fio_file *, enum fio_ddir); extern void unlock_file(struct thread_data *, struct fio_file *); extern void unlock_file_all(struct thread_data *, struct fio_file *); extern int add_dir_files(struct thread_data *, const char *); extern int init_random_map(struct thread_data *); extern void dup_files(struct thread_data *, struct thread_data *); extern int get_fileno(struct thread_data *, const char *); extern void free_release_files(struct thread_data *); void fio_file_reset(struct thread_data *, struct fio_file *); #endif fio-2.1.3/filehash.c000066400000000000000000000040571222032232000142130ustar00rootroot00000000000000#include #include #include "fio.h" #include "flist.h" #include "hash.h" #define HASH_BUCKETS 512 #define HASH_MASK (HASH_BUCKETS - 1) unsigned int file_hash_size = HASH_BUCKETS * sizeof(struct flist_head); static struct flist_head *file_hash; static struct fio_mutex *hash_lock; static unsigned short hash(const char *name) { return jhash(name, strlen(name), 0) & HASH_MASK; } void remove_file_hash(struct fio_file *f) { fio_mutex_down(hash_lock); if (fio_file_hashed(f)) { assert(!flist_empty(&f->hash_list)); flist_del_init(&f->hash_list); fio_file_clear_hashed(f); } fio_mutex_up(hash_lock); } static struct fio_file *__lookup_file_hash(const char *name) { struct flist_head *bucket = &file_hash[hash(name)]; struct flist_head *n; flist_for_each(n, bucket) { struct fio_file *f = flist_entry(n, struct fio_file, hash_list); if (!f->file_name) continue; if (!strcmp(f->file_name, name)) { assert(f->fd != -1); return f; } } return NULL; } struct fio_file *lookup_file_hash(const char *name) { struct fio_file *f; fio_mutex_down(hash_lock); f = __lookup_file_hash(name); fio_mutex_up(hash_lock); return f; } struct fio_file *add_file_hash(struct fio_file *f) { struct fio_file *alias; if (fio_file_hashed(f)) return NULL; INIT_FLIST_HEAD(&f->hash_list); fio_mutex_down(hash_lock); alias = __lookup_file_hash(f->file_name); if (!alias) { fio_file_set_hashed(f); flist_add_tail(&f->hash_list, &file_hash[hash(f->file_name)]); } fio_mutex_up(hash_lock); return alias; } void file_hash_exit(void) { unsigned int i, has_entries = 0; fio_mutex_down(hash_lock); for (i = 0; i < HASH_BUCKETS; i++) has_entries += !flist_empty(&file_hash[i]); fio_mutex_up(hash_lock); if (has_entries) log_err("fio: file hash not empty on exit\n"); file_hash = NULL; fio_mutex_remove(hash_lock); hash_lock = NULL; } void file_hash_init(void *ptr) { unsigned int i; file_hash = ptr; for (i = 0; i < HASH_BUCKETS; i++) INIT_FLIST_HEAD(&file_hash[i]); hash_lock = fio_mutex_init(FIO_MUTEX_UNLOCKED); } fio-2.1.3/filehash.h000066400000000000000000000005071222032232000142140ustar00rootroot00000000000000#ifndef FIO_FILE_HASH_H #define FIO_FILE_HASH_H extern unsigned int file_hash_size; extern void file_hash_init(void *); extern void file_hash_exit(void); extern struct fio_file *lookup_file_hash(const char *); extern struct fio_file *add_file_hash(struct fio_file *); extern void remove_file_hash(struct fio_file *); #endif fio-2.1.3/filesetup.c000066400000000000000000000702731222032232000144330ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "fio.h" #include "smalloc.h" #include "filehash.h" #include "os/os.h" #include "hash.h" #include "lib/axmap.h" #ifdef CONFIG_LINUX_FALLOCATE #include #endif static int root_warn; static inline void clear_error(struct thread_data *td) { td->error = 0; td->verror[0] = '\0'; } /* * Leaves f->fd open on success, caller must close */ static int extend_file(struct thread_data *td, struct fio_file *f) { int r, new_layout = 0, unlink_file = 0, flags; unsigned long long left; unsigned int bs; char *b; if (read_only) { log_err("fio: refusing extend of file due to read-only\n"); return 0; } /* * check if we need to lay the file out complete again. fio * does that for operations involving reads, or for writes * where overwrite is set */ if (td_read(td) || (td_write(td) && td->o.overwrite) || (td_write(td) && td->io_ops->flags & FIO_NOEXTEND)) new_layout = 1; if (td_write(td) && !td->o.overwrite) unlink_file = 1; if (unlink_file || new_layout) { dprint(FD_FILE, "layout unlink %s\n", f->file_name); if ((unlink(f->file_name) < 0) && (errno != ENOENT)) { td_verror(td, errno, "unlink"); return 1; } } flags = O_WRONLY | O_CREAT; if (new_layout) flags |= O_TRUNC; dprint(FD_FILE, "open file %s, flags %x\n", f->file_name, flags); f->fd = open(f->file_name, flags, 0644); if (f->fd < 0) { td_verror(td, errno, "open"); return 1; } #ifdef CONFIG_POSIX_FALLOCATE if (!td->o.fill_device) { switch (td->o.fallocate_mode) { case FIO_FALLOCATE_NONE: break; case FIO_FALLOCATE_POSIX: dprint(FD_FILE, "posix_fallocate file %s size %llu\n", f->file_name, (unsigned long long) f->real_file_size); r = posix_fallocate(f->fd, 0, f->real_file_size); if (r > 0) { log_err("fio: posix_fallocate fails: %s\n", strerror(r)); } break; #ifdef CONFIG_LINUX_FALLOCATE case FIO_FALLOCATE_KEEP_SIZE: dprint(FD_FILE, "fallocate(FALLOC_FL_KEEP_SIZE) " "file %s size %llu\n", f->file_name, (unsigned long long) f->real_file_size); r = fallocate(f->fd, FALLOC_FL_KEEP_SIZE, 0, f->real_file_size); if (r != 0) td_verror(td, errno, "fallocate"); break; #endif /* CONFIG_LINUX_FALLOCATE */ default: log_err("fio: unknown fallocate mode: %d\n", td->o.fallocate_mode); assert(0); } } #endif /* CONFIG_POSIX_FALLOCATE */ if (!new_layout) goto done; /* * The size will be -1ULL when fill_device is used, so don't truncate * or fallocate this file, just write it */ if (!td->o.fill_device) { dprint(FD_FILE, "truncate file %s, size %llu\n", f->file_name, (unsigned long long) f->real_file_size); if (ftruncate(f->fd, f->real_file_size) == -1) { td_verror(td, errno, "ftruncate"); goto err; } } b = malloc(td->o.max_bs[DDIR_WRITE]); left = f->real_file_size; while (left && !td->terminate) { bs = td->o.max_bs[DDIR_WRITE]; if (bs > left) bs = left; fill_io_buffer(td, b, bs, bs); r = write(f->fd, b, bs); if (r > 0) { left -= r; continue; } else { if (r < 0) { int __e = errno; if (__e == ENOSPC) { if (td->o.fill_device) break; log_info("fio: ENOSPC on laying out " "file, stopping\n"); break; } td_verror(td, errno, "write"); } else td_verror(td, EIO, "write"); break; } } if (td->terminate) { dprint(FD_FILE, "terminate unlink %s\n", f->file_name); unlink(f->file_name); } else if (td->o.create_fsync) { if (fsync(f->fd) < 0) { td_verror(td, errno, "fsync"); goto err; } } if (td->o.fill_device && !td_write(td)) { fio_file_clear_size_known(f); if (td_io_get_file_size(td, f)) goto err; if (f->io_size > f->real_file_size) f->io_size = f->real_file_size; } free(b); done: return 0; err: close(f->fd); f->fd = -1; return 1; } static int pre_read_file(struct thread_data *td, struct fio_file *f) { int r, did_open = 0, old_runstate; unsigned long long left; unsigned int bs; char *b; if (td->io_ops->flags & FIO_PIPEIO) return 0; if (!fio_file_open(f)) { if (td->io_ops->open_file(td, f)) { log_err("fio: cannot pre-read, failed to open file\n"); return 1; } did_open = 1; } old_runstate = td->runstate; td_set_runstate(td, TD_PRE_READING); bs = td->o.max_bs[DDIR_READ]; b = malloc(bs); memset(b, 0, bs); lseek(f->fd, f->file_offset, SEEK_SET); left = f->io_size; while (left && !td->terminate) { if (bs > left) bs = left; r = read(f->fd, b, bs); if (r == (int) bs) { left -= bs; continue; } else { td_verror(td, EIO, "pre_read"); break; } } td_set_runstate(td, old_runstate); if (did_open) td->io_ops->close_file(td, f); free(b); return 0; } static unsigned long long get_rand_file_size(struct thread_data *td) { unsigned long long ret, sized; unsigned long r; if (td->o.use_os_rand) { r = os_random_long(&td->file_size_state); sized = td->o.file_size_high - td->o.file_size_low; ret = (unsigned long long) ((double) sized * (r / (OS_RAND_MAX + 1.0))); } else { r = __rand(&td->__file_size_state); sized = td->o.file_size_high - td->o.file_size_low; ret = (unsigned long long) ((double) sized * (r / (FRAND_MAX + 1.0))); } ret += td->o.file_size_low; ret -= (ret % td->o.rw_min_bs); return ret; } static int file_size(struct thread_data *td, struct fio_file *f) { struct stat st; if (stat(f->file_name, &st) == -1) { td_verror(td, errno, "fstat"); return 1; } f->real_file_size = st.st_size; return 0; } static int bdev_size(struct thread_data *td, struct fio_file *f) { unsigned long long bytes = 0; int r; if (td->io_ops->open_file(td, f)) { log_err("fio: failed opening blockdev %s for size check\n", f->file_name); return 1; } r = blockdev_size(f, &bytes); if (r) { td_verror(td, r, "blockdev_size"); goto err; } if (!bytes) { log_err("%s: zero sized block device?\n", f->file_name); goto err; } f->real_file_size = bytes; td->io_ops->close_file(td, f); return 0; err: td->io_ops->close_file(td, f); return 1; } static int char_size(struct thread_data *td, struct fio_file *f) { #ifdef FIO_HAVE_CHARDEV_SIZE unsigned long long bytes = 0; int r; if (td->io_ops->open_file(td, f)) { log_err("fio: failed opening blockdev %s for size check\n", f->file_name); return 1; } r = chardev_size(f, &bytes); if (r) { td_verror(td, r, "chardev_size"); goto err; } if (!bytes) { log_err("%s: zero sized char device?\n", f->file_name); goto err; } f->real_file_size = bytes; td->io_ops->close_file(td, f); return 0; err: td->io_ops->close_file(td, f); return 1; #else f->real_file_size = -1ULL; return 0; #endif } static int get_file_size(struct thread_data *td, struct fio_file *f) { int ret = 0; if (fio_file_size_known(f)) return 0; if (f->filetype == FIO_TYPE_FILE) ret = file_size(td, f); else if (f->filetype == FIO_TYPE_BD) ret = bdev_size(td, f); else if (f->filetype == FIO_TYPE_CHAR) ret = char_size(td, f); else f->real_file_size = -1; if (ret) return ret; if (f->file_offset > f->real_file_size) { log_err("%s: offset extends end (%llu > %llu)\n", td->o.name, (unsigned long long) f->file_offset, (unsigned long long) f->real_file_size); return 1; } fio_file_set_size_known(f); return 0; } static int __file_invalidate_cache(struct thread_data *td, struct fio_file *f, unsigned long long off, unsigned long long len) { int ret = 0; if (len == -1ULL) len = f->io_size; if (off == -1ULL) off = f->file_offset; if (len == -1ULL || off == -1ULL) return 0; dprint(FD_IO, "invalidate cache %s: %llu/%llu\n", f->file_name, off, len); /* * FIXME: add blockdev flushing too */ if (f->mmap_ptr) { ret = posix_madvise(f->mmap_ptr, f->mmap_sz, POSIX_MADV_DONTNEED); #ifdef FIO_MADV_FREE if (f->filetype == FIO_TYPE_BD) (void) posix_madvise(f->mmap_ptr, f->mmap_sz, FIO_MADV_FREE); #endif } else if (f->filetype == FIO_TYPE_FILE) { ret = posix_fadvise(f->fd, off, len, POSIX_FADV_DONTNEED); } else if (f->filetype == FIO_TYPE_BD) { ret = blockdev_invalidate_cache(f); if (ret < 0 && errno == EACCES && geteuid()) { if (!root_warn) { log_err("fio: only root may flush block " "devices. Cache flush bypassed!\n"); root_warn = 1; } ret = 0; } } else if (f->filetype == FIO_TYPE_CHAR || f->filetype == FIO_TYPE_PIPE) ret = 0; if (ret < 0) { td_verror(td, errno, "invalidate_cache"); return 1; } else if (ret > 0) { td_verror(td, ret, "invalidate_cache"); return 1; } return ret; } int file_invalidate_cache(struct thread_data *td, struct fio_file *f) { if (!fio_file_open(f)) return 0; return __file_invalidate_cache(td, f, -1ULL, -1ULL); } int generic_close_file(struct thread_data fio_unused *td, struct fio_file *f) { int ret = 0; dprint(FD_FILE, "fd close %s\n", f->file_name); remove_file_hash(f); if (close(f->fd) < 0) ret = errno; f->fd = -1; if (f->shadow_fd != -1) { close(f->shadow_fd); f->shadow_fd = -1; } f->engine_data = 0; return ret; } int file_lookup_open(struct fio_file *f, int flags) { struct fio_file *__f; int from_hash; __f = lookup_file_hash(f->file_name); if (__f) { dprint(FD_FILE, "found file in hash %s\n", f->file_name); /* * racy, need the __f->lock locked */ f->lock = __f->lock; from_hash = 1; } else { dprint(FD_FILE, "file not found in hash %s\n", f->file_name); from_hash = 0; } f->fd = open(f->file_name, flags, 0600); return from_hash; } static int file_close_shadow_fds(struct thread_data *td) { struct fio_file *f; int num_closed = 0; unsigned int i; for_each_file(td, f, i) { if (f->shadow_fd == -1) continue; close(f->shadow_fd); f->shadow_fd = -1; num_closed++; } return num_closed; } int generic_open_file(struct thread_data *td, struct fio_file *f) { int is_std = 0; int flags = 0; int from_hash = 0; dprint(FD_FILE, "fd open %s\n", f->file_name); if (td_trim(td) && f->filetype != FIO_TYPE_BD) { log_err("fio: trim only applies to block device\n"); return 1; } if (!strcmp(f->file_name, "-")) { if (td_rw(td)) { log_err("fio: can't read/write to stdin/out\n"); return 1; } is_std = 1; /* * move output logging to stderr, if we are writing to stdout */ if (td_write(td)) f_out = stderr; } if (td_trim(td)) goto skip_flags; if (td->o.odirect) flags |= OS_O_DIRECT; if (td->o.sync_io) flags |= O_SYNC; if (td->o.create_on_open) flags |= O_CREAT; skip_flags: if (f->filetype != FIO_TYPE_FILE) flags |= FIO_O_NOATIME; open_again: if (td_write(td)) { if (!read_only) flags |= O_RDWR; if (f->filetype == FIO_TYPE_FILE) flags |= O_CREAT; if (is_std) f->fd = dup(STDOUT_FILENO); else from_hash = file_lookup_open(f, flags); } else if (td_read(td)) { if (f->filetype == FIO_TYPE_CHAR && !read_only) flags |= O_RDWR; else flags |= O_RDONLY; if (is_std) f->fd = dup(STDIN_FILENO); else from_hash = file_lookup_open(f, flags); } else { //td trim flags |= O_RDWR; from_hash = file_lookup_open(f, flags); } if (f->fd == -1) { char buf[FIO_VERROR_SIZE]; int __e = errno; if (__e == EPERM && (flags & FIO_O_NOATIME)) { flags &= ~FIO_O_NOATIME; goto open_again; } if (__e == EMFILE && file_close_shadow_fds(td)) goto open_again; snprintf(buf, sizeof(buf), "open(%s)", f->file_name); if (__e == EINVAL && (flags & OS_O_DIRECT)) { log_err("fio: looks like your file system does not " \ "support direct=1/buffered=0\n"); } td_verror(td, __e, buf); } if (!from_hash && f->fd != -1) { if (add_file_hash(f)) { int fio_unused ret; /* * Stash away descriptor for later close. This is to * work-around a "feature" on Linux, where a close of * an fd that has been opened for write will trigger * udev to call blkid to check partitions, fs id, etc. * That polutes the device cache, which can slow down * unbuffered accesses. */ if (f->shadow_fd == -1) f->shadow_fd = f->fd; else { /* * OK to ignore, we haven't done anything * with it */ ret = generic_close_file(td, f); } goto open_again; } } return 0; } int generic_get_file_size(struct thread_data *td, struct fio_file *f) { return get_file_size(td, f); } /* * open/close all files, so that ->real_file_size gets set */ static int get_file_sizes(struct thread_data *td) { struct fio_file *f; unsigned int i; int err = 0; for_each_file(td, f, i) { dprint(FD_FILE, "get file size for %p/%d/%p\n", f, i, f->file_name); if (td_io_get_file_size(td, f)) { if (td->error != ENOENT) { log_err("%s\n", td->verror); err = 1; } clear_error(td); } if (f->real_file_size == -1ULL && td->o.size) f->real_file_size = td->o.size / td->o.nr_files; } return err; } struct fio_mount { struct flist_head list; const char *base; char __base[256]; unsigned int key; }; /* * Get free number of bytes for each file on each unique mount. */ static unsigned long long get_fs_free_counts(struct thread_data *td) { struct flist_head *n, *tmp; unsigned long long ret = 0; struct fio_mount *fm; FLIST_HEAD(list); struct fio_file *f; unsigned int i; for_each_file(td, f, i) { struct stat sb; char buf[256]; if (f->filetype == FIO_TYPE_BD || f->filetype == FIO_TYPE_CHAR) { if (f->real_file_size != -1ULL) ret += f->real_file_size; continue; } else if (f->filetype != FIO_TYPE_FILE) continue; strcpy(buf, f->file_name); if (stat(buf, &sb) < 0) { if (errno != ENOENT) break; strcpy(buf, "."); if (stat(buf, &sb) < 0) break; } fm = NULL; flist_for_each(n, &list) { fm = flist_entry(n, struct fio_mount, list); if (fm->key == sb.st_dev) break; fm = NULL; } if (fm) continue; fm = malloc(sizeof(*fm)); strcpy(fm->__base, buf); fm->base = basename(fm->__base); fm->key = sb.st_dev; flist_add(&fm->list, &list); } flist_for_each_safe(n, tmp, &list) { unsigned long long sz; fm = flist_entry(n, struct fio_mount, list); flist_del(&fm->list); sz = get_fs_size(fm->base); if (sz && sz != -1ULL) ret += sz; free(fm); } return ret; } uint64_t get_start_offset(struct thread_data *td) { return td->o.start_offset + (td->thread_number - 1) * td->o.offset_increment; } /* * Open the files and setup files sizes, creating files if necessary. */ int setup_files(struct thread_data *td) { unsigned long long total_size, extend_size; struct thread_options *o = &td->o; struct fio_file *f; unsigned int i; int err = 0, need_extend; int old_state; dprint(FD_FILE, "setup files\n"); old_state = td->runstate; td_set_runstate(td, TD_SETTING_UP); if (o->read_iolog_file) goto done; /* * if ioengine defines a setup() method, it's responsible for * opening the files and setting f->real_file_size to indicate * the valid range for that file. */ if (td->io_ops->setup) err = td->io_ops->setup(td); else err = get_file_sizes(td); if (err) goto err_out; /* * check sizes. if the files/devices do not exist and the size * isn't passed to fio, abort. */ total_size = 0; for_each_file(td, f, i) { if (f->real_file_size == -1ULL) total_size = -1ULL; else total_size += f->real_file_size; } if (o->fill_device) td->fill_device_size = get_fs_free_counts(td); /* * device/file sizes are zero and no size given, punt */ if ((!total_size || total_size == -1ULL) && !o->size && !(td->io_ops->flags & FIO_NOIO) && !o->fill_device && !(o->nr_files && (o->file_size_low || o->file_size_high))) { log_err("%s: you need to specify size=\n", o->name); td_verror(td, EINVAL, "total_file_size"); goto err_out; } /* * now file sizes are known, so we can set ->io_size. if size= is * not given, ->io_size is just equal to ->real_file_size. if size * is given, ->io_size is size / nr_files. */ extend_size = total_size = 0; need_extend = 0; for_each_file(td, f, i) { f->file_offset = get_start_offset(td); if (!o->file_size_low) { /* * no file size range given, file size is equal to * total size divided by number of files. if that is * zero, set it to the real file size. */ f->io_size = o->size / o->nr_files; if (!f->io_size) f->io_size = f->real_file_size - f->file_offset; } else if (f->real_file_size < o->file_size_low || f->real_file_size > o->file_size_high) { if (f->file_offset > o->file_size_low) goto err_offset; /* * file size given. if it's fixed, use that. if it's a * range, generate a random size in-between. */ if (o->file_size_low == o->file_size_high) f->io_size = o->file_size_low - f->file_offset; else { f->io_size = get_rand_file_size(td) - f->file_offset; } } else f->io_size = f->real_file_size - f->file_offset; if (f->io_size == -1ULL) total_size = -1ULL; else { if (o->size_percent) f->io_size = (f->io_size * o->size_percent) / 100; total_size += f->io_size; } if (f->filetype == FIO_TYPE_FILE && (f->io_size + f->file_offset) > f->real_file_size && !(td->io_ops->flags & FIO_DISKLESSIO)) { if (!o->create_on_open) { need_extend++; extend_size += (f->io_size + f->file_offset); } else f->real_file_size = f->io_size + f->file_offset; fio_file_set_extend(f); } } if (!o->size || o->size > total_size) o->size = total_size; /* * See if we need to extend some files */ if (need_extend) { temp_stall_ts = 1; if (output_format == FIO_OUTPUT_NORMAL) log_info("%s: Laying out IO file(s) (%u file(s) /" " %lluMB)\n", o->name, need_extend, extend_size >> 20); for_each_file(td, f, i) { unsigned long long old_len = -1ULL, extend_len = -1ULL; if (!fio_file_extend(f)) continue; assert(f->filetype == FIO_TYPE_FILE); fio_file_clear_extend(f); if (!o->fill_device) { old_len = f->real_file_size; extend_len = f->io_size + f->file_offset - old_len; } f->real_file_size = (f->io_size + f->file_offset); err = extend_file(td, f); if (err) break; err = __file_invalidate_cache(td, f, old_len, extend_len); close(f->fd); f->fd = -1; if (err) break; } temp_stall_ts = 0; } if (err) goto err_out; if (!o->zone_size) o->zone_size = o->size; /* * iolog already set the total io size, if we read back * stored entries. */ if (!o->read_iolog_file) td->total_io_size = o->size * o->loops; done: if (o->create_only) td->done = 1; td_set_runstate(td, old_state); return 0; err_offset: log_err("%s: you need to specify valid offset=\n", o->name); err_out: td_set_runstate(td, old_state); return 1; } int pre_read_files(struct thread_data *td) { struct fio_file *f; unsigned int i; dprint(FD_FILE, "pre_read files\n"); for_each_file(td, f, i) { pre_read_file(td, f); } return 1; } static int __init_rand_distribution(struct thread_data *td, struct fio_file *f) { unsigned int range_size, seed; unsigned long nranges; uint64_t file_size; range_size = min(td->o.min_bs[DDIR_READ], td->o.min_bs[DDIR_WRITE]); file_size = min(f->real_file_size, f->io_size); nranges = (file_size + range_size - 1) / range_size; seed = jhash(f->file_name, strlen(f->file_name), 0) * td->thread_number; if (!td->o.rand_repeatable) seed = td->rand_seeds[4]; if (td->o.random_distribution == FIO_RAND_DIST_ZIPF) zipf_init(&f->zipf, nranges, td->o.zipf_theta.u.f, seed); else pareto_init(&f->zipf, nranges, td->o.pareto_h.u.f, seed); return 1; } static int init_rand_distribution(struct thread_data *td) { struct fio_file *f; unsigned int i; int state; if (td->o.random_distribution == FIO_RAND_DIST_RANDOM) return 0; state = td->runstate; td_set_runstate(td, TD_SETTING_UP); for_each_file(td, f, i) __init_rand_distribution(td, f); td_set_runstate(td, state); return 1; } int init_random_map(struct thread_data *td) { unsigned long long blocks; struct fio_file *f; unsigned int i; if (init_rand_distribution(td)) return 0; if (!td_random(td)) return 0; for_each_file(td, f, i) { uint64_t file_size = min(f->real_file_size, f->io_size); blocks = file_size / (unsigned long long) td->o.rw_min_bs; if (td->o.random_generator == FIO_RAND_GEN_LFSR) { unsigned long seed; seed = td->rand_seeds[FIO_RAND_BLOCK_OFF]; if (!lfsr_init(&f->lfsr, blocks, seed, seed & 0xF)) continue; } else if (!td->o.norandommap) { f->io_axmap = axmap_new(blocks); if (f->io_axmap) continue; } else if (td->o.norandommap) continue; if (!td->o.softrandommap) { log_err("fio: failed allocating random map. If running" " a large number of jobs, try the 'norandommap'" " option or set 'softrandommap'. Or give" " a larger --alloc-size to fio.\n"); return 1; } log_info("fio: file %s failed allocating random map. Running " "job without.\n", f->file_name); } return 0; } void close_files(struct thread_data *td) { struct fio_file *f; unsigned int i; for_each_file(td, f, i) { if (fio_file_open(f)) td_io_close_file(td, f); } } void close_and_free_files(struct thread_data *td) { struct fio_file *f; unsigned int i; dprint(FD_FILE, "close files\n"); for_each_file(td, f, i) { if (td->o.unlink && f->filetype == FIO_TYPE_FILE) { dprint(FD_FILE, "free unlink %s\n", f->file_name); unlink(f->file_name); } if (fio_file_open(f)) td_io_close_file(td, f); remove_file_hash(f); sfree(f->file_name); f->file_name = NULL; axmap_free(f->io_axmap); f->io_axmap = NULL; sfree(f); } td->o.filename = NULL; free(td->files); free(td->file_locks); td->files_index = 0; td->files = NULL; td->file_locks = NULL; td->o.nr_files = 0; } static void get_file_type(struct fio_file *f) { struct stat sb; if (!strcmp(f->file_name, "-")) f->filetype = FIO_TYPE_PIPE; else f->filetype = FIO_TYPE_FILE; /* \\.\ is the device namespace in Windows, where every file is * a block device */ if (strncmp(f->file_name, "\\\\.\\", 4) == 0) f->filetype = FIO_TYPE_BD; if (!stat(f->file_name, &sb)) { if (S_ISBLK(sb.st_mode)) f->filetype = FIO_TYPE_BD; else if (S_ISCHR(sb.st_mode)) f->filetype = FIO_TYPE_CHAR; else if (S_ISFIFO(sb.st_mode)) f->filetype = FIO_TYPE_PIPE; } } int add_file(struct thread_data *td, const char *fname) { int cur_files = td->files_index; char file_name[PATH_MAX]; struct fio_file *f; int len = 0; dprint(FD_FILE, "add file %s\n", fname); f = smalloc(sizeof(*f)); if (!f) { log_err("fio: smalloc OOM\n"); assert(0); } f->fd = -1; f->shadow_fd = -1; fio_file_reset(td, f); if (td->files_size <= td->files_index) { unsigned int new_size = td->o.nr_files + 1; dprint(FD_FILE, "resize file array to %d files\n", new_size); td->files = realloc(td->files, new_size * sizeof(f)); if (td->files == NULL) { log_err("fio: realloc OOM\n"); assert(0); } if (td->o.file_lock_mode != FILE_LOCK_NONE) { td->file_locks = realloc(td->file_locks, new_size); if (!td->file_locks) { log_err("fio: realloc OOM\n"); assert(0); } td->file_locks[cur_files] = FILE_LOCK_NONE; } td->files_size = new_size; } td->files[cur_files] = f; f->fileno = cur_files; /* * init function, io engine may not be loaded yet */ if (td->io_ops && (td->io_ops->flags & FIO_DISKLESSIO)) f->real_file_size = -1ULL; if (td->o.directory) len = sprintf(file_name, "%s/", td->o.directory); sprintf(file_name + len, "%s", fname); f->file_name = smalloc_strdup(file_name); if (!f->file_name) { log_err("fio: smalloc OOM\n"); assert(0); } get_file_type(f); switch (td->o.file_lock_mode) { case FILE_LOCK_NONE: break; case FILE_LOCK_READWRITE: f->rwlock = fio_rwlock_init(); break; case FILE_LOCK_EXCLUSIVE: f->lock = fio_mutex_init(FIO_MUTEX_UNLOCKED); break; default: log_err("fio: unknown lock mode: %d\n", td->o.file_lock_mode); assert(0); } td->files_index++; if (f->filetype == FIO_TYPE_FILE) td->nr_normal_files++; dprint(FD_FILE, "file %p \"%s\" added at %d\n", f, f->file_name, cur_files); return cur_files; } int add_file_exclusive(struct thread_data *td, const char *fname) { struct fio_file *f; unsigned int i; for_each_file(td, f, i) { if (!strcmp(f->file_name, fname)) return i; } return add_file(td, fname); } void get_file(struct fio_file *f) { dprint(FD_FILE, "get file %s, ref=%d\n", f->file_name, f->references); assert(fio_file_open(f)); f->references++; } int put_file(struct thread_data *td, struct fio_file *f) { int f_ret = 0, ret = 0; dprint(FD_FILE, "put file %s, ref=%d\n", f->file_name, f->references); if (!fio_file_open(f)) { assert(f->fd == -1); return 0; } assert(f->references); if (--f->references) return 0; if (should_fsync(td) && td->o.fsync_on_close) f_ret = fsync(f->fd); if (td->io_ops->close_file) ret = td->io_ops->close_file(td, f); if (!ret) ret = f_ret; td->nr_open_files--; fio_file_clear_open(f); assert(f->fd == -1); return ret; } void lock_file(struct thread_data *td, struct fio_file *f, enum fio_ddir ddir) { if (!f->lock || td->o.file_lock_mode == FILE_LOCK_NONE) return; if (td->o.file_lock_mode == FILE_LOCK_READWRITE) { if (ddir == DDIR_READ) fio_rwlock_read(f->rwlock); else fio_rwlock_write(f->rwlock); } else if (td->o.file_lock_mode == FILE_LOCK_EXCLUSIVE) fio_mutex_down(f->lock); td->file_locks[f->fileno] = td->o.file_lock_mode; } void unlock_file(struct thread_data *td, struct fio_file *f) { if (!f->lock || td->o.file_lock_mode == FILE_LOCK_NONE) return; if (td->o.file_lock_mode == FILE_LOCK_READWRITE) fio_rwlock_unlock(f->rwlock); else if (td->o.file_lock_mode == FILE_LOCK_EXCLUSIVE) fio_mutex_up(f->lock); td->file_locks[f->fileno] = FILE_LOCK_NONE; } void unlock_file_all(struct thread_data *td, struct fio_file *f) { if (td->file_locks[f->fileno] != FILE_LOCK_NONE) unlock_file(td, f); } static int recurse_dir(struct thread_data *td, const char *dirname) { struct dirent *dir; int ret = 0; DIR *D; D = opendir(dirname); if (!D) { char buf[FIO_VERROR_SIZE]; snprintf(buf, FIO_VERROR_SIZE, "opendir(%s)", dirname); td_verror(td, errno, buf); return 1; } while ((dir = readdir(D)) != NULL) { char full_path[PATH_MAX]; struct stat sb; if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")) continue; sprintf(full_path, "%s%s%s", dirname, FIO_OS_PATH_SEPARATOR, dir->d_name); if (lstat(full_path, &sb) == -1) { if (errno != ENOENT) { td_verror(td, errno, "stat"); return 1; } } if (S_ISREG(sb.st_mode)) { add_file(td, full_path); td->o.nr_files++; continue; } if (!S_ISDIR(sb.st_mode)) continue; ret = recurse_dir(td, full_path); if (ret) break; } closedir(D); return ret; } int add_dir_files(struct thread_data *td, const char *path) { int ret = recurse_dir(td, path); if (!ret) log_info("fio: opendir added %d files\n", td->o.nr_files); return ret; } void dup_files(struct thread_data *td, struct thread_data *org) { struct fio_file *f; unsigned int i; dprint(FD_FILE, "dup files: %d\n", org->files_index); if (!org->files) return; td->files = malloc(org->files_index * sizeof(f)); if (td->o.file_lock_mode != FILE_LOCK_NONE) td->file_locks = malloc(org->files_index); for_each_file(org, f, i) { struct fio_file *__f; __f = smalloc(sizeof(*__f)); if (!__f) { log_err("fio: smalloc OOM\n"); assert(0); } __f->fd = -1; fio_file_reset(td, __f); if (f->file_name) { __f->file_name = smalloc_strdup(f->file_name); if (!__f->file_name) { log_err("fio: smalloc OOM\n"); assert(0); } __f->filetype = f->filetype; } td->files[i] = __f; } } /* * Returns the index that matches the filename, or -1 if not there */ int get_fileno(struct thread_data *td, const char *fname) { struct fio_file *f; unsigned int i; for_each_file(td, f, i) if (!strcmp(f->file_name, fname)) return i; return -1; } /* * For log usage, where we add/open/close files automatically */ void free_release_files(struct thread_data *td) { close_files(td); td->files_index = 0; td->nr_normal_files = 0; } void fio_file_reset(struct thread_data *td, struct fio_file *f) { f->last_pos = f->file_offset; f->last_start = -1ULL; if (f->io_axmap) axmap_reset(f->io_axmap); if (td->o.random_generator == FIO_RAND_GEN_LFSR) lfsr_reset(&f->lfsr, td->rand_seeds[FIO_RAND_BLOCK_OFF]); } fio-2.1.3/fio.1000066400000000000000000001613071222032232000131250ustar00rootroot00000000000000.TH fio 1 "September 2007" "User Manual" .SH NAME fio \- flexible I/O tester .SH SYNOPSIS .B fio [\fIoptions\fR] [\fIjobfile\fR]... .SH DESCRIPTION .B fio is a tool that will spawn a number of threads or processes doing a particular type of I/O action as specified by the user. The typical use of fio is to write a job file matching the I/O load one wants to simulate. .SH OPTIONS .TP .BI \-\-debug \fR=\fPtype Enable verbose tracing of various fio actions. May be `all' for all types or individual types separated by a comma (eg \-\-debug=io,file). `help' will list all available tracing options. .TP .BI \-\-output \fR=\fPfilename Write output to \fIfilename\fR. .TP .BI \-\-runtime \fR=\fPruntime Limit run time to \fIruntime\fR seconds. .TP .B \-\-latency\-log Generate per-job latency logs. .TP .B \-\-bandwidth\-log Generate per-job bandwidth logs. .TP .B \-\-minimal Print statistics in a terse, semicolon-delimited format. .TP .B \-\-version Display version information and exit. .TP .BI \-\-terse\-version \fR=\fPversion Set terse version output format (Current version 3, or older version 2). .TP .B \-\-help Display usage information and exit. .TP .BI \-\-cmdhelp \fR=\fPcommand Print help information for \fIcommand\fR. May be `all' for all commands. .TP .BI \-\-enghelp \fR=\fPioengine[,command] List all commands defined by \fIioengine\fR, or print help for \fIcommand\fR defined by \fIioengine\fR. .TP .BI \-\-showcmd \fR=\fPjobfile Convert \fIjobfile\fR to a set of command-line options. .TP .BI \-\-eta \fR=\fPwhen Specifies when real-time ETA estimate should be printed. \fIwhen\fR may be one of `always', `never' or `auto'. .TP .BI \-\-eta\-newline \fR=\fPtime Force an ETA newline for every `time` period passed. .TP .BI \-\-status\-interval \fR=\fPtime Report full output status every `time` period passed. .TP .BI \-\-readonly Turn on safety read-only checks, preventing any attempted write. .TP .BI \-\-section \fR=\fPsec Only run section \fIsec\fR from job file. Multiple of these options can be given, adding more sections to run. .TP .BI \-\-alloc\-size \fR=\fPkb Set the internal smalloc pool size to \fIkb\fP kilobytes. .TP .BI \-\-warnings\-fatal All fio parser warnings are fatal, causing fio to exit with an error. .TP .BI \-\-max\-jobs \fR=\fPnr Set the maximum allowed number of jobs (threads/processes) to support. .TP .BI \-\-server \fR=\fPargs Start a backend server, with \fIargs\fP specifying what to listen to. See client/server section. .TP .BI \-\-daemonize \fR=\fPpidfile Background a fio server, writing the pid to the given pid file. .TP .BI \-\-client \fR=\fPhost Instead of running the jobs locally, send and run them on the given host. .TP .BI \-\-idle\-prof \fR=\fPoption Report cpu idleness on a system or percpu basis (\fIoption\fP=system,percpu) or run unit work calibration only (\fIoption\fP=calibrate). .SH "JOB FILE FORMAT" Job files are in `ini' format. They consist of one or more job definitions, which begin with a job name in square brackets and extend to the next job name. The job name can be any ASCII string except `global', which has a special meaning. Following the job name is a sequence of zero or more parameters, one per line, that define the behavior of the job. Any line starting with a `;' or `#' character is considered a comment and ignored. .P If \fIjobfile\fR is specified as `-', the job file will be read from standard input. .SS "Global Section" The global section contains default parameters for jobs specified in the job file. A job is only affected by global sections residing above it, and there may be any number of global sections. Specific job definitions may override any parameter set in global sections. .SH "JOB PARAMETERS" .SS Types Some parameters may take arguments of a specific type. The types used are: .TP .I str String: a sequence of alphanumeric characters. .TP .I int SI integer: a whole number, possibly containing a suffix denoting the base unit of the value. Accepted suffixes are `k', 'M', 'G', 'T', and 'P', denoting kilo (1024), mega (1024^2), giga (1024^3), tera (1024^4), and peta (1024^5) respectively. The suffix is not case sensitive. If prefixed with '0x', the value is assumed to be base 16 (hexadecimal). A suffix may include a trailing 'b', for instance 'kb' is identical to 'k'. You can specify a base 10 value by using 'KiB', 'MiB', 'GiB', etc. This is useful for disk drives where values are often given in base 10 values. Specifying '30GiB' will get you 30*1000^3 bytes. .TP .I bool Boolean: a true or false value. `0' denotes false, `1' denotes true. .TP .I irange Integer range: a range of integers specified in the format \fIlower\fR:\fIupper\fR or \fIlower\fR\-\fIupper\fR. \fIlower\fR and \fIupper\fR may contain a suffix as described above. If an option allows two sets of ranges, they are separated with a `,' or `/' character. For example: `8\-8k/8M\-4G'. .TP .I float_list List of floating numbers: A list of floating numbers, separated by a ':' charcater. .SS "Parameter List" .TP .BI name \fR=\fPstr May be used to override the job name. On the command line, this parameter has the special purpose of signalling the start of a new job. .TP .BI description \fR=\fPstr Human-readable description of the job. It is printed when the job is run, but otherwise has no special purpose. .TP .BI directory \fR=\fPstr Prefix filenames with this directory. Used to place files in a location other than `./'. .TP .BI filename \fR=\fPstr .B fio normally makes up a file name based on the job name, thread number, and file number. If you want to share files between threads in a job or several jobs, specify a \fIfilename\fR for each of them to override the default. If the I/O engine is file-based, you can specify a number of files by separating the names with a `:' character. `\-' is a reserved name, meaning stdin or stdout, depending on the read/write direction set. .TP .BI filename_format \fR=\fPstr If sharing multiple files between jobs, it is usually necessary to have fio generate the exact names that you want. By default, fio will name a file based on the default file format specification of \fBjobname.jobnumber.filenumber\fP. With this option, that can be customized. Fio will recognize and replace the following keywords in this string: .RS .RS .TP .B $jobname The name of the worker thread or process. .TP .B $jobnum The incremental number of the worker thread or process. .TP .B $filenum The incremental number of the file for that worker thread or process. .RE .P To have dependent jobs share a set of files, this option can be set to have fio generate filenames that are shared between the two. For instance, if \fBtestfiles.$filenum\fR is specified, file number 4 for any job will be named \fBtestfiles.4\fR. The default of \fB$jobname.$jobnum.$filenum\fR will be used if no other format specifier is given. .RE .P .TP .BI lockfile \fR=\fPstr Fio defaults to not locking any files before it does IO to them. If a file or file descriptor is shared, fio can serialize IO to that file to make the end result consistent. This is usual for emulating real workloads that share files. The lock modes are: .RS .RS .TP .B none No locking. This is the default. .TP .B exclusive Only one thread or process may do IO at the time, excluding all others. .TP .B readwrite Read-write locking on the file. Many readers may access the file at the same time, but writes get exclusive access. .RE .RE .P .BI opendir \fR=\fPstr Recursively open any files below directory \fIstr\fR. .TP .BI readwrite \fR=\fPstr "\fR,\fP rw" \fR=\fPstr Type of I/O pattern. Accepted values are: .RS .RS .TP .B read Sequential reads. .TP .B write Sequential writes. .TP .B randread Random reads. .TP .B randwrite Random writes. .TP .B rw, readwrite Mixed sequential reads and writes. .TP .B randrw Mixed random reads and writes. .RE .P For mixed I/O, the default split is 50/50. For certain types of io the result may still be skewed a bit, since the speed may be different. It is possible to specify a number of IO's to do before getting a new offset, this is done by appending a `:\fI\fR to the end of the string given. For a random read, it would look like \fBrw=randread:8\fR for passing in an offset modifier with a value of 8. If the postfix is used with a sequential IO pattern, then the value specified will be added to the generated offset for each IO. For instance, using \fBrw=write:4k\fR will skip 4k for every write. It turns sequential IO into sequential IO with holes. See the \fBrw_sequencer\fR option. .RE .TP .BI rw_sequencer \fR=\fPstr If an offset modifier is given by appending a number to the \fBrw=\fR line, then this option controls how that number modifies the IO offset being generated. Accepted values are: .RS .RS .TP .B sequential Generate sequential offset .TP .B identical Generate the same offset .RE .P \fBsequential\fR is only useful for random IO, where fio would normally generate a new random offset for every IO. If you append eg 8 to randread, you would get a new random offset for every 8 IO's. The result would be a seek for only every 8 IO's, instead of for every IO. Use \fBrw=randread:8\fR to specify that. As sequential IO is already sequential, setting \fBsequential\fR for that would not result in any differences. \fBidentical\fR behaves in a similar fashion, except it sends the same offset 8 number of times before generating a new offset. .RE .P .TP .BI kb_base \fR=\fPint The base unit for a kilobyte. The defacto base is 2^10, 1024. Storage manufacturers like to use 10^3 or 1000 as a base ten unit instead, for obvious reasons. Allow values are 1024 or 1000, with 1024 being the default. .TP .BI unified_rw_reporting \fR=\fPbool Fio normally reports statistics on a per data direction basis, meaning that read, write, and trim are accounted and reported separately. If this option is set, the fio will sum the results and report them as "mixed" instead. .TP .BI randrepeat \fR=\fPbool Seed the random number generator in a predictable way so results are repeatable across runs. Default: true. .TP .BI use_os_rand \fR=\fPbool Fio can either use the random generator supplied by the OS to generator random offsets, or it can use it's own internal generator (based on Tausworthe). Default is to use the internal generator, which is often of better quality and faster. Default: false. .TP .BI fallocate \fR=\fPstr Whether pre-allocation is performed when laying down files. Accepted values are: .RS .RS .TP .B none Do not pre-allocate space. .TP .B posix Pre-allocate via posix_fallocate(). .TP .B keep Pre-allocate via fallocate() with FALLOC_FL_KEEP_SIZE set. .TP .B 0 Backward-compatible alias for 'none'. .TP .B 1 Backward-compatible alias for 'posix'. .RE .P May not be available on all supported platforms. 'keep' is only available on Linux. If using ZFS on Solaris this must be set to 'none' because ZFS doesn't support it. Default: 'posix'. .RE .TP .BI fadvise_hint \fR=\fPbool Use of \fIposix_fadvise\fR\|(2) to advise the kernel what I/O patterns are likely to be issued. Default: true. .TP .BI size \fR=\fPint Total size of I/O for this job. \fBfio\fR will run until this many bytes have been transferred, unless limited by other options (\fBruntime\fR, for instance). Unless \fBnrfiles\fR and \fBfilesize\fR options are given, this amount will be divided between the available files for the job. If not set, fio will use the full size of the given files or devices. If the the files do not exist, size must be given. It is also possible to give size as a percentage between 1 and 100. If size=20% is given, fio will use 20% of the full size of the given files or devices. .TP .BI fill_device \fR=\fPbool "\fR,\fB fill_fs" \fR=\fPbool Sets size to something really large and waits for ENOSPC (no space left on device) as the terminating condition. Only makes sense with sequential write. For a read workload, the mount point will be filled first then IO started on the result. This option doesn't make sense if operating on a raw device node, since the size of that is already known by the file system. Additionally, writing beyond end-of-device will not return ENOSPC there. .TP .BI filesize \fR=\fPirange Individual file sizes. May be a range, in which case \fBfio\fR will select sizes for files at random within the given range, limited to \fBsize\fR in total (if that is given). If \fBfilesize\fR is not specified, each created file is the same size. .TP .BI blocksize \fR=\fPint[,int] "\fR,\fB bs" \fR=\fPint[,int] Block size for I/O units. Default: 4k. Values for reads, writes, and trims can be specified separately in the format \fIread\fR,\fIwrite\fR,\fItrim\fR either of which may be empty to leave that value at its default. If a trailing comma isn't given, the remainder will inherit the last value set. .TP .BI blocksize_range \fR=\fPirange[,irange] "\fR,\fB bsrange" \fR=\fPirange[,irange] Specify a range of I/O block sizes. The issued I/O unit will always be a multiple of the minimum size, unless \fBblocksize_unaligned\fR is set. Applies to both reads and writes if only one range is given, but can be specified separately with a comma seperating the values. Example: bsrange=1k-4k,2k-8k. Also (see \fBblocksize\fR). .TP .BI bssplit \fR=\fPstr This option allows even finer grained control of the block sizes issued, not just even splits between them. With this option, you can weight various block sizes for exact control of the issued IO for a job that has mixed block sizes. The format of the option is bssplit=blocksize/percentage, optionally adding as many definitions as needed separated by a colon. Example: bssplit=4k/10:64k/50:32k/40 would issue 50% 64k blocks, 10% 4k blocks and 40% 32k blocks. \fBbssplit\fR also supports giving separate splits to reads and writes. The format is identical to what the \fBbs\fR option accepts, the read and write parts are separated with a comma. .TP .B blocksize_unaligned\fR,\fP bs_unaligned If set, any size in \fBblocksize_range\fR may be used. This typically won't work with direct I/O, as that normally requires sector alignment. .TP .BI blockalign \fR=\fPint[,int] "\fR,\fB ba" \fR=\fPint[,int] At what boundary to align random IO offsets. Defaults to the same as 'blocksize' the minimum blocksize given. Minimum alignment is typically 512b for using direct IO, though it usually depends on the hardware block size. This option is mutually exclusive with using a random map for files, so it will turn off that option. .TP .BI bs_is_seq_rand \fR=\fPbool If this option is set, fio will use the normal read,write blocksize settings as sequential,random instead. Any random read or write will use the WRITE blocksize settings, and any sequential read or write will use the READ blocksize setting. .TP .B zero_buffers Initialise buffers with all zeros. Default: fill buffers with random data. .TP .B refill_buffers If this option is given, fio will refill the IO buffers on every submit. The default is to only fill it at init time and reuse that data. Only makes sense if zero_buffers isn't specified, naturally. If data verification is enabled, refill_buffers is also automatically enabled. .TP .BI scramble_buffers \fR=\fPbool If \fBrefill_buffers\fR is too costly and the target is using data deduplication, then setting this option will slightly modify the IO buffer contents to defeat normal de-dupe attempts. This is not enough to defeat more clever block compression attempts, but it will stop naive dedupe of blocks. Default: true. .TP .BI buffer_compress_percentage \fR=\fPint If this is set, then fio will attempt to provide IO buffer content (on WRITEs) that compress to the specified level. Fio does this by providing a mix of random data and zeroes. Note that this is per block size unit, for file/disk wide compression level that matches this setting, you'll also want to set \fBrefill_buffers\fR. .TP .BI buffer_compress_chunk \fR=\fPint See \fBbuffer_compress_percentage\fR. This setting allows fio to manage how big the ranges of random data and zeroed data is. Without this set, fio will provide \fBbuffer_compress_percentage\fR of blocksize random data, followed by the remaining zeroed. With this set to some chunk size smaller than the block size, fio can alternate random and zeroed data throughout the IO buffer. .TP .BI nrfiles \fR=\fPint Number of files to use for this job. Default: 1. .TP .BI openfiles \fR=\fPint Number of files to keep open at the same time. Default: \fBnrfiles\fR. .TP .BI file_service_type \fR=\fPstr Defines how files to service are selected. The following types are defined: .RS .RS .TP .B random Choose a file at random .TP .B roundrobin Round robin over open files (default). .B sequential Do each file in the set sequentially. .RE .P The number of I/Os to issue before switching a new file can be specified by appending `:\fIint\fR' to the service type. .RE .TP .BI ioengine \fR=\fPstr Defines how the job issues I/O. The following types are defined: .RS .RS .TP .B sync Basic \fIread\fR\|(2) or \fIwrite\fR\|(2) I/O. \fIfseek\fR\|(2) is used to position the I/O location. .TP .B psync Basic \fIpread\fR\|(2) or \fIpwrite\fR\|(2) I/O. .TP .B vsync Basic \fIreadv\fR\|(2) or \fIwritev\fR\|(2) I/O. Will emulate queuing by coalescing adjacents IOs into a single submission. .TP .B pvsync Basic \fIpreadv\fR\|(2) or \fIpwritev\fR\|(2) I/O. .TP .B libaio Linux native asynchronous I/O. This ioengine defines engine specific options. .TP .B posixaio POSIX asynchronous I/O using \fIaio_read\fR\|(3) and \fIaio_write\fR\|(3). .TP .B solarisaio Solaris native asynchronous I/O. .TP .B windowsaio Windows native asynchronous I/O. .TP .B mmap File is memory mapped with \fImmap\fR\|(2) and data copied using \fImemcpy\fR\|(3). .TP .B splice \fIsplice\fR\|(2) is used to transfer the data and \fIvmsplice\fR\|(2) to transfer data from user-space to the kernel. .TP .B syslet-rw Use the syslet system calls to make regular read/write asynchronous. .TP .B sg SCSI generic sg v3 I/O. May be either synchronous using the SG_IO ioctl, or if the target is an sg character device, we use \fIread\fR\|(2) and \fIwrite\fR\|(2) for asynchronous I/O. .TP .B null Doesn't transfer any data, just pretends to. Mainly used to exercise \fBfio\fR itself and for debugging and testing purposes. .TP .B net Transfer over the network. The protocol to be used can be defined with the \fBprotocol\fR parameter. Depending on the protocol, \fBfilename\fR, \fBhostname\fR, \fBport\fR, or \fBlisten\fR must be specified. This ioengine defines engine specific options. .TP .B netsplice Like \fBnet\fR, but uses \fIsplice\fR\|(2) and \fIvmsplice\fR\|(2) to map data and send/receive. This ioengine defines engine specific options. .TP .B cpuio Doesn't transfer any data, but burns CPU cycles according to \fBcpuload\fR and \fBcpucycles\fR parameters. .TP .B guasi The GUASI I/O engine is the Generic Userspace Asynchronous Syscall Interface approach to asycnronous I/O. .br See . .TP .B rdma The RDMA I/O engine supports both RDMA memory semantics (RDMA_WRITE/RDMA_READ) and channel semantics (Send/Recv) for the InfiniBand, RoCE and iWARP protocols. .TP .B external Loads an external I/O engine object file. Append the engine filename as `:\fIenginepath\fR'. .TP .B falloc IO engine that does regular linux native fallocate callt to simulate data transfer as fio ioengine .br DDIR_READ does fallocate(,mode = FALLOC_FL_KEEP_SIZE,) .br DIR_WRITE does fallocate(,mode = 0) .br DDIR_TRIM does fallocate(,mode = FALLOC_FL_KEEP_SIZE|FALLOC_FL_PUNCH_HOLE) .TP .B e4defrag IO engine that does regular EXT4_IOC_MOVE_EXT ioctls to simulate defragment activity request to DDIR_WRITE event .RE .P .RE .TP .BI iodepth \fR=\fPint Number of I/O units to keep in flight against the file. 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 \fBdirect\fR=1, since buffered IO is not async on that OS. Keep an eye on the IO depth distribution in the fio output to verify that the achieved depth is as expected. Default: 1. .TP .BI iodepth_batch \fR=\fPint Number of I/Os to submit at once. Default: \fBiodepth\fR. .TP .BI iodepth_batch_complete \fR=\fPint This defines how many pieces of IO to retrieve at once. It defaults to 1 which means that we'll ask for a minimum of 1 IO in the retrieval process from the kernel. The IO retrieval will go on until we hit the limit set by \fBiodepth_low\fR. If this variable is set to 0, then fio will always check for completed events before queuing more IO. This helps reduce IO latency, at the cost of more retrieval system calls. .TP .BI iodepth_low \fR=\fPint Low watermark indicating when to start filling the queue again. Default: \fBiodepth\fR. .TP .BI direct \fR=\fPbool If true, use non-buffered I/O (usually O_DIRECT). Default: false. .TP .BI buffered \fR=\fPbool If true, use buffered I/O. This is the opposite of the \fBdirect\fR parameter. Default: true. .TP .BI offset \fR=\fPint Offset in the file to start I/O. Data before the offset will not be touched. .TP .BI offset_increment \fR=\fPint If this is provided, then the real offset becomes the offset + offset_increment * thread_number, where the thread number is a counter that starts at 0 and is incremented for each job. This option is useful if there are several jobs which are intended to operate on a file in parallel in disjoint segments, with even spacing between the starting points. .TP .BI number_ios \fR=\fPint Fio will normally perform IOs until it has exhausted the size of the region set by \fBsize\fR, or if it exhaust the allocated time (or hits an error condition). With this setting, the range/size can be set independently of the number of IOs to perform. When fio reaches this number, it will exit normally and report status. .TP .BI fsync \fR=\fPint How many I/Os to perform before issuing an \fBfsync\fR\|(2) of dirty data. If 0, don't sync. Default: 0. .TP .BI fdatasync \fR=\fPint Like \fBfsync\fR, but uses \fBfdatasync\fR\|(2) instead to only sync the data parts of the file. Default: 0. .TP .BI sync_file_range \fR=\fPstr:int Use sync_file_range() for every \fRval\fP number of write operations. Fio will track range of writes that have happened since the last sync_file_range() call. \fRstr\fP can currently be one or more of: .RS .TP .B wait_before SYNC_FILE_RANGE_WAIT_BEFORE .TP .B write SYNC_FILE_RANGE_WRITE .TP .B wait_after SYNC_FILE_RANGE_WRITE .TP .RE .P So if you do sync_file_range=wait_before,write:8, fio would use \fBSYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE\fP for every 8 writes. Also see the sync_file_range(2) man page. This option is Linux specific. .TP .BI overwrite \fR=\fPbool If writing, setup the file first and do overwrites. Default: false. .TP .BI end_fsync \fR=\fPbool Sync file contents when a write stage has completed. Default: false. .TP .BI fsync_on_close \fR=\fPbool If true, sync file contents on close. This differs from \fBend_fsync\fR in that it will happen on every close, not just at the end of the job. Default: false. .TP .BI rwmixread \fR=\fPint Percentage of a mixed workload that should be reads. Default: 50. .TP .BI rwmixwrite \fR=\fPint Percentage of a mixed workload that should be writes. If \fBrwmixread\fR and \fBrwmixwrite\fR are given and do not sum to 100%, the latter of the two overrides the first. This may interfere with a given rate setting, if fio is asked to limit reads or writes to a certain rate. If that is the case, then the distribution may be skewed. Default: 50. .TP .BI random_distribution \fR=\fPstr:float By default, fio will use a completely uniform random distribution when asked to perform random IO. Sometimes it is useful to skew the distribution in specific ways, ensuring that some parts of the data is more hot than others. Fio includes the following distribution models: .RS .TP .B random Uniform random distribution .TP .B zipf Zipf distribution .TP .B pareto Pareto distribution .TP .RE .P When using a zipf or pareto distribution, an input value is also needed to define the access pattern. For zipf, this is the zipf theta. For pareto, it's the pareto power. Fio includes a test program, genzipf, that can be used visualize what the given input values will yield in terms of hit rates. If you wanted to use zipf with a theta of 1.2, you would use random_distribution=zipf:1.2 as the option. If a non-uniform model is used, fio will disable use of the random map. .TP .BI percentage_random \fR=\fPint For a random workload, set how big a percentage should be random. This defaults to 100%, in which case the workload is fully random. It can be set from anywhere from 0 to 100. Setting it to 0 would make the workload fully sequential. It is possible to set different values for reads, writes, and trim. To do so, simply use a comma separated list. See \fBblocksize\fR. .TP .B norandommap Normally \fBfio\fR will cover every block of the file when doing random I/O. If this parameter is given, a new offset will be chosen without looking at past I/O history. This parameter is mutually exclusive with \fBverify\fR. .TP .BI softrandommap \fR=\fPbool See \fBnorandommap\fR. If fio runs with the random block map enabled and it fails to allocate the map, if this option is set it will continue without a random block map. As coverage will not be as complete as with random maps, this option is disabled by default. .TP .BI random_generator \fR=\fPstr Fio supports the following engines for generating IO offsets for random IO: .RS .TP .B tausworthe Strong 2^88 cycle random number generator .TP .B lfsr Linear feedback shift register generator .TP .RE .P Tausworthe is a strong random number generator, but it requires tracking on the side if we want to ensure that blocks are only read or written once. LFSR guarantees that we never generate the same offset twice, and it's also less computationally expensive. It's not a true random generator, however, though for IO purposes it's typically good enough. LFSR only works with single block sizes, not with workloads that use multiple block sizes. If used with such a workload, fio may read or write some blocks multiple times. .TP .BI nice \fR=\fPint Run job with given nice value. See \fInice\fR\|(2). .TP .BI prio \fR=\fPint Set I/O priority value of this job between 0 (highest) and 7 (lowest). See \fIionice\fR\|(1). .TP .BI prioclass \fR=\fPint Set I/O priority class. See \fIionice\fR\|(1). .TP .BI thinktime \fR=\fPint Stall job for given number of microseconds between issuing I/Os. .TP .BI thinktime_spin \fR=\fPint Pretend to spend CPU time for given number of microseconds, sleeping the rest of the time specified by \fBthinktime\fR. Only valid if \fBthinktime\fR is set. .TP .BI thinktime_blocks \fR=\fPint Only valid if thinktime is set - control how many blocks to issue, before waiting \fBthinktime\fR microseconds. If not set, defaults to 1 which will make fio wait \fBthinktime\fR microseconds after every block. This effectively makes any queue depth setting redundant, since no more than 1 IO will be queued before we have to complete it and do our thinktime. In other words, this setting effectively caps the queue depth if the latter is larger. Default: 1. .TP .BI rate \fR=\fPint Cap bandwidth used by this job. The number is in bytes/sec, the normal postfix rules apply. You can use \fBrate\fR=500k to limit reads and writes to 500k each, or you can specify read and writes separately. Using \fBrate\fR=1m,500k would limit reads to 1MB/sec and writes to 500KB/sec. Capping only reads or writes can be done with \fBrate\fR=,500k or \fBrate\fR=500k,. The former will only limit writes (to 500KB/sec), the latter will only limit reads. .TP .BI ratemin \fR=\fPint Tell \fBfio\fR to do whatever it can to maintain at least the given bandwidth. Failing to meet this requirement will cause the job to exit. The same format as \fBrate\fR is used for read vs write separation. .TP .BI rate_iops \fR=\fPint Cap the bandwidth to this number of IOPS. Basically the same as rate, just specified independently of bandwidth. The same format as \fBrate\fR is used for read vs write seperation. If \fBblocksize\fR is a range, the smallest block size is used as the metric. .TP .BI rate_iops_min \fR=\fPint If this rate of I/O is not met, the job will exit. The same format as \fBrate\fR is used for read vs write seperation. .TP .BI ratecycle \fR=\fPint Average bandwidth for \fBrate\fR and \fBratemin\fR over this number of milliseconds. Default: 1000ms. .TP .BI max_latency \fR=\fPint If set, fio will exit the job if it exceeds this maximum latency. It will exit with an ETIME error. .TP .BI cpumask \fR=\fPint Set CPU affinity for this job. \fIint\fR is a bitmask of allowed CPUs the job may run on. See \fBsched_setaffinity\fR\|(2). .TP .BI cpus_allowed \fR=\fPstr Same as \fBcpumask\fR, but allows a comma-delimited list of CPU numbers. .TP .BI numa_cpu_nodes \fR=\fPstr Set this job running on spcified NUMA nodes' CPUs. The arguments allow comma delimited list of cpu numbers, A-B ranges, or 'all'. .TP .BI numa_mem_policy \fR=\fPstr Set this job's memory policy and corresponding NUMA nodes. Format of the argements: .RS .TP .B [:] .TP .B mode is one of the following memory policy: .TP .B default, prefer, bind, interleave, local .TP .RE For \fBdefault\fR and \fBlocal\fR memory policy, no \fBnodelist\fR is needed to be specified. For \fBprefer\fR, only one node is allowed. For \fBbind\fR and \fBinterleave\fR, \fBnodelist\fR allows comma delimited list of numbers, A-B ranges, or 'all'. .TP .BI startdelay \fR=\fPint Delay start of job for the specified number of seconds. .TP .BI runtime \fR=\fPint Terminate processing after the specified number of seconds. .TP .B time_based If given, run for the specified \fBruntime\fR duration even if the files are completely read or written. The same workload will be repeated as many times as \fBruntime\fR allows. .TP .BI ramp_time \fR=\fPint If set, fio will run the specified workload for this amount of time before logging any performance numbers. Useful for letting performance settle before logging results, thus minimizing the runtime required for stable results. Note that the \fBramp_time\fR is considered lead in time for a job, thus it will increase the total runtime if a special timeout or runtime is specified. .TP .BI invalidate \fR=\fPbool Invalidate buffer-cache for the file prior to starting I/O. Default: true. .TP .BI sync \fR=\fPbool Use synchronous I/O for buffered writes. For the majority of I/O engines, this means using O_SYNC. Default: false. .TP .BI iomem \fR=\fPstr "\fR,\fP mem" \fR=\fPstr Allocation method for I/O unit buffer. Allowed values are: .RS .RS .TP .B malloc Allocate memory with \fImalloc\fR\|(3). .TP .B shm Use shared memory buffers allocated through \fIshmget\fR\|(2). .TP .B shmhuge Same as \fBshm\fR, but use huge pages as backing. .TP .B mmap Use \fImmap\fR\|(2) for allocation. Uses anonymous memory unless a filename is given after the option in the format `:\fIfile\fR'. .TP .B mmaphuge Same as \fBmmap\fR, but use huge files as backing. .RE .P The amount of memory allocated is the maximum allowed \fBblocksize\fR for the job multiplied by \fBiodepth\fR. For \fBshmhuge\fR or \fBmmaphuge\fR to work, the system must have free huge pages allocated. \fBmmaphuge\fR also needs to have hugetlbfs mounted, and \fIfile\fR must point there. At least on Linux, huge pages must be manually allocated. See \fB/proc/sys/vm/nr_hugehages\fR and the documentation for that. Normally you just need to echo an appropriate number, eg echoing 8 will ensure that the OS has 8 huge pages ready for use. .RE .TP .BI iomem_align \fR=\fPint "\fR,\fP mem_align" \fR=\fPint This indiciates the memory alignment of the IO memory buffers. Note that the given alignment is applied to the first IO unit buffer, if using \fBiodepth\fR the alignment of the following buffers are given by the \fBbs\fR used. In other words, if using a \fBbs\fR that is a multiple of the page sized in the system, all buffers will be aligned to this value. If using a \fBbs\fR that is not page aligned, the alignment of subsequent IO memory buffers is the sum of the \fBiomem_align\fR and \fBbs\fR used. .TP .BI hugepage\-size \fR=\fPint Defines the size of a huge page. Must be at least equal to the system setting. Should be a multiple of 1MB. Default: 4MB. .TP .B exitall Terminate all jobs when one finishes. Default: wait for each job to finish. .TP .BI bwavgtime \fR=\fPint Average bandwidth calculations over the given time in milliseconds. Default: 500ms. .TP .BI iopsavgtime \fR=\fPint Average IOPS calculations over the given time in milliseconds. Default: 500ms. .TP .BI create_serialize \fR=\fPbool If true, serialize file creation for the jobs. Default: true. .TP .BI create_fsync \fR=\fPbool \fIfsync\fR\|(2) data file after creation. Default: true. .TP .BI create_on_open \fR=\fPbool If true, the files are not created until they are opened for IO by the job. .TP .BI create_only \fR=\fPbool If true, fio will only run the setup phase of the job. If files need to be laid out or updated on disk, only that will be done. The actual job contents are not executed. .TP .BI pre_read \fR=\fPbool If this is given, files will be pre-read into memory before starting the given IO operation. This will also clear the \fR \fBinvalidate\fR flag, since it is pointless to pre-read and then drop the cache. This will only work for IO engines that are seekable, since they allow you to read the same data multiple times. Thus it will not work on eg network or splice IO. .TP .BI unlink \fR=\fPbool Unlink job files when done. Default: false. .TP .BI loops \fR=\fPint Specifies the number of iterations (runs of the same workload) of this job. Default: 1. .TP .BI do_verify \fR=\fPbool Run the verify phase after a write phase. Only valid if \fBverify\fR is set. Default: true. .TP .BI verify \fR=\fPstr Method of verifying file contents after each iteration of the job. Allowed values are: .RS .RS .TP .B md5 crc16 crc32 crc32c crc32c-intel crc64 crc7 sha256 sha512 sha1 Store appropriate checksum in the header of each block. crc32c-intel is hardware accelerated SSE4.2 driven, falls back to regular crc32c if not supported by the system. .TP .B meta Write extra information about each I/O (timestamp, block number, etc.). The block number is verified. See \fBverify_pattern\fR as well. .TP .B null Pretend to verify. Used for testing internals. .RE This option can be used for repeated burn-in tests of a system to make sure that the written data is also correctly read back. If the data direction given is a read or random read, fio will assume that it should verify a previously written file. If the data direction includes any form of write, the verify will be of the newly written data. .RE .TP .BI verify_sort \fR=\fPbool If true, written verify blocks are sorted if \fBfio\fR deems it to be faster to read them back in a sorted manner. Default: true. .TP .BI verify_offset \fR=\fPint Swap the verification header with data somewhere else in the block before writing. It is swapped back before verifying. .TP .BI verify_interval \fR=\fPint Write the verification header for this number of bytes, which should divide \fBblocksize\fR. Default: \fBblocksize\fR. .TP .BI verify_pattern \fR=\fPstr If set, fio will fill the io buffers with this pattern. Fio defaults to filling with totally random bytes, but sometimes it's interesting to fill with a known pattern for io verification purposes. Depending on the width of the pattern, fio will fill 1/2/3/4 bytes of the buffer at the time(it can be either a decimal or a hex number). The verify_pattern if larger than a 32-bit quantity has to be a hex number that starts with either "0x" or "0X". Use with \fBverify\fP=meta. .TP .BI verify_fatal \fR=\fPbool If true, exit the job on the first observed verification failure. Default: false. .TP .BI verify_dump \fR=\fPbool If set, dump the contents of both the original data block and the data block we read off disk to files. This allows later analysis to inspect just what kind of data corruption occurred. Off by default. .TP .BI verify_async \fR=\fPint Fio will normally verify IO inline from the submitting thread. This option takes an integer describing how many async offload threads to create for IO verification instead, causing fio to offload the duty of verifying IO contents to one or more separate threads. If using this offload option, even sync IO engines can benefit from using an \fBiodepth\fR setting higher than 1, as it allows them to have IO in flight while verifies are running. .TP .BI verify_async_cpus \fR=\fPstr Tell fio to set the given CPU affinity on the async IO verification threads. See \fBcpus_allowed\fP for the format used. .TP .BI verify_backlog \fR=\fPint Fio will normally verify the written contents of a job that utilizes verify once that job has completed. In other words, everything is written then everything is read back and verified. You may want to verify continually instead for a variety of reasons. Fio stores the meta data associated with an IO block in memory, so for large verify workloads, quite a bit of memory would be used up holding this meta data. If this option is enabled, fio will write only N blocks before verifying these blocks. .TP .BI verify_backlog_batch \fR=\fPint Control how many blocks fio will verify if verify_backlog is set. If not set, will default to the value of \fBverify_backlog\fR (meaning the entire queue is read back and verified). If \fBverify_backlog_batch\fR is less than \fBverify_backlog\fR then not all blocks will be verified, if \fBverify_backlog_batch\fR is larger than \fBverify_backlog\fR, some blocks will be verified more than once. .TP .B stonewall "\fR,\fP wait_for_previous" Wait for preceding jobs in the job file to exit before starting this one. \fBstonewall\fR implies \fBnew_group\fR. .TP .B new_group Start a new reporting group. If not given, all jobs in a file will be part of the same reporting group, unless separated by a stonewall. .TP .BI numjobs \fR=\fPint Number of clones (processes/threads performing the same workload) of this job. Default: 1. .TP .B group_reporting If set, display per-group reports instead of per-job when \fBnumjobs\fR is specified. .TP .B thread Use threads created with \fBpthread_create\fR\|(3) instead of processes created with \fBfork\fR\|(2). .TP .BI zonesize \fR=\fPint Divide file into zones of the specified size in bytes. See \fBzoneskip\fR. .TP .BI zoneskip \fR=\fPint Skip the specified number of bytes when \fBzonesize\fR bytes of data have been read. .TP .BI write_iolog \fR=\fPstr Write the issued I/O patterns to the specified file. Specify a separate file for each job, otherwise the iologs will be interspersed and the file may be corrupt. .TP .BI read_iolog \fR=\fPstr Replay the I/O patterns contained in the specified file generated by \fBwrite_iolog\fR, or may be a \fBblktrace\fR binary file. .TP .BI replay_no_stall \fR=\fPint While replaying I/O patterns using \fBread_iolog\fR the default behavior attempts to respect timing information between I/Os. Enabling \fBreplay_no_stall\fR causes I/Os to be replayed as fast as possible while still respecting ordering. .TP .BI replay_redirect \fR=\fPstr While replaying I/O patterns using \fBread_iolog\fR the default behavior is to replay the IOPS onto the major/minor device that each IOP was recorded from. Setting \fBreplay_redirect\fR causes all IOPS to be replayed onto the single specified device regardless of the device it was recorded from. .TP .BI write_bw_log \fR=\fPstr If given, write a bandwidth log of the jobs in this job file. Can be used to store data of the bandwidth of the jobs in their lifetime. The included fio_generate_plots script uses gnuplot to turn these text files into nice graphs. See \fBwrite_log_log\fR for behaviour of given filename. For this option, the postfix is _bw.log. .TP .BI write_lat_log \fR=\fPstr Same as \fBwrite_bw_log\fR, but writes I/O completion latencies. If no filename is given with this option, the default filename of "jobname_type.log" is used. Even if the filename is given, fio will still append the type of log. .TP .BI write_iops_log \fR=\fPstr Same as \fBwrite_bw_log\fR, but writes IOPS. If no filename is given with this option, the default filename of "jobname_type.log" is used. Even if the filename is given, fio will still append the type of log. .TP .BI log_avg_msec \fR=\fPint By default, fio will log an entry in the iops, latency, or bw log for every IO that completes. When writing to the disk log, that can quickly grow to a very large size. Setting this option makes fio average the each log entry over the specified period of time, reducing the resolution of the log. Defaults to 0. .TP .BI disable_lat \fR=\fPbool Disable measurements of total latency numbers. Useful only for cutting back the number of calls to gettimeofday, as that does impact performance at really high IOPS rates. Note that to really get rid of a large amount of these calls, this option must be used with disable_slat and disable_bw as well. .TP .BI disable_clat \fR=\fPbool Disable measurements of completion latency numbers. See \fBdisable_lat\fR. .TP .BI disable_slat \fR=\fPbool Disable measurements of submission latency numbers. See \fBdisable_lat\fR. .TP .BI disable_bw_measurement \fR=\fPbool Disable measurements of throughput/bandwidth numbers. See \fBdisable_lat\fR. .TP .BI lockmem \fR=\fPint Pin the specified amount of memory with \fBmlock\fR\|(2). Can be used to simulate a smaller amount of memory. The amount specified is per worker. .TP .BI exec_prerun \fR=\fPstr Before running the job, execute the specified command with \fBsystem\fR\|(3). .RS Output is redirected in a file called \fBjobname.prerun.txt\fR .RE .TP .BI exec_postrun \fR=\fPstr Same as \fBexec_prerun\fR, but the command is executed after the job completes. .RS Output is redirected in a file called \fBjobname.postrun.txt\fR .RE .TP .BI ioscheduler \fR=\fPstr Attempt to switch the device hosting the file to the specified I/O scheduler. .TP .BI cpuload \fR=\fPint If the job is a CPU cycle-eater, attempt to use the specified percentage of CPU cycles. .TP .BI cpuchunks \fR=\fPint If the job is a CPU cycle-eater, split the load into cycles of the given time in milliseconds. .TP .BI disk_util \fR=\fPbool Generate disk utilization statistics if the platform supports it. Default: true. .TP .BI clocksource \fR=\fPstr Use the given clocksource as the base of timing. The supported options are: .RS .TP .B gettimeofday gettimeofday(2) .TP .B clock_gettime clock_gettime(2) .TP .B cpu Internal CPU clock source .TP .RE .P \fBcpu\fR is the preferred clocksource if it is reliable, as it is very fast (and fio is heavy on time calls). Fio will automatically use this clocksource if it's supported and considered reliable on the system it is running on, unless another clocksource is specifically set. For x86/x86-64 CPUs, this means supporting TSC Invariant. .TP .BI gtod_reduce \fR=\fPbool Enable all of the gettimeofday() reducing options (disable_clat, disable_slat, disable_bw) plus reduce precision of the timeout somewhat to really shrink the gettimeofday() call count. With this option enabled, we only do about 0.4% of the gtod() calls we would have done if all time keeping was enabled. .TP .BI gtod_cpu \fR=\fPint Sometimes it's cheaper to dedicate a single thread of execution to just getting the current time. Fio (and databases, for instance) are very intensive on gettimeofday() calls. With this option, you can set one CPU aside for doing nothing but logging current time to a shared memory location. Then the other threads/processes that run IO workloads need only copy that segment, instead of entering the kernel with a gettimeofday() call. The CPU set aside for doing these time calls will be excluded from other uses. Fio will manually clear it from the CPU mask of other jobs. .TP .BI ignore_error \fR=\fPstr Sometimes you want to ignore some errors during test in that case you can specify error list for each error type. .br ignore_error=READ_ERR_LIST,WRITE_ERR_LIST,VERIFY_ERR_LIST .br errors for given error type is separated with ':'. Error may be symbol ('ENOSPC', 'ENOMEM') or an integer. .br Example: ignore_error=EAGAIN,ENOSPC:122 . .br This option will ignore EAGAIN from READ, and ENOSPC and 122(EDQUOT) from WRITE. .TP .BI error_dump \fR=\fPbool If set dump every error even if it is non fatal, true by default. If disabled only fatal error will be dumped .TP .BI cgroup \fR=\fPstr Add job to this control group. If it doesn't exist, it will be created. The system must have a mounted cgroup blkio mount point for this to work. If your system doesn't have it mounted, you can do so with: # mount \-t cgroup \-o blkio none /cgroup .TP .BI cgroup_weight \fR=\fPint Set the weight of the cgroup to this value. See the documentation that comes with the kernel, allowed values are in the range of 100..1000. .TP .BI cgroup_nodelete \fR=\fPbool Normally fio will delete the cgroups it has created after the job completion. To override this behavior and to leave cgroups around after the job completion, set cgroup_nodelete=1. This can be useful if one wants to inspect various cgroup files after job completion. Default: false .TP .BI uid \fR=\fPint Instead of running as the invoking user, set the user ID to this value before the thread/process does any work. .TP .BI gid \fR=\fPint Set group ID, see \fBuid\fR. .TP .BI flow_id \fR=\fPint The ID of the flow. If not specified, it defaults to being a global flow. See \fBflow\fR. .TP .BI flow \fR=\fPint Weight in token-based flow control. If this value is used, then there is a \fBflow counter\fR which is used to regulate the proportion of activity between two or more jobs. fio attempts to keep this flow counter near zero. The \fBflow\fR parameter stands for how much should be added or subtracted to the flow counter on each iteration of the main I/O loop. That is, if one job has \fBflow=8\fR and another job has \fBflow=-1\fR, then there will be a roughly 1:8 ratio in how much one runs vs the other. .TP .BI flow_watermark \fR=\fPint The maximum value that the absolute value of the flow counter is allowed to reach before the job must wait for a lower value of the counter. .TP .BI flow_sleep \fR=\fPint The period of time, in microseconds, to wait after the flow watermark has been exceeded before retrying operations .TP .BI clat_percentiles \fR=\fPbool Enable the reporting of percentiles of completion latencies. .TP .BI percentile_list \fR=\fPfloat_list Overwrite the default list of percentiles for completion latencies. Each number is a floating number in the range (0,100], and the maximum length of the list is 20. Use ':' to separate the numbers. For example, \-\-percentile_list=99.5:99.9 will cause fio to report the values of completion latency below which 99.5% and 99.9% of the observed latencies fell, respectively. .SS "Ioengine Parameters List" Some parameters are only valid when a specific ioengine is in use. These are used identically to normal parameters, with the caveat that when used on the command line, the must come after the ioengine that defines them is selected. .TP .BI (cpu)cpuload \fR=\fPint Attempt to use the specified percentage of CPU cycles. .TP .BI (cpu)cpuchunks \fR=\fPint Split the load into cycles of the given time. In microseconds. .TP .BI (libaio)userspace_reap Normally, with the libaio engine in use, fio will use the io_getevents system call to reap newly returned events. With this flag turned on, the AIO ring will be read directly from user-space to reap events. The reaping mode is only enabled when polling for a minimum of 0 events (eg when iodepth_batch_complete=0). .TP .BI (net,netsplice)hostname \fR=\fPstr The host name or IP address to use for TCP or UDP based IO. If the job is a TCP listener or UDP reader, the hostname is not used and must be omitted unless it is a valid UDP multicast address. .TP .BI (net,netsplice)port \fR=\fPint The TCP or UDP port to bind to or connect to. .TP .BI (net,netsplice)interface \fR=\fPstr The IP address of the network interface used to send or receive UDP multicast packets. .TP .BI (net,netsplice)ttl \fR=\fPint Time-to-live value for outgoing UDP multicast packets. Default: 1 .TP .BI (net,netsplice)nodelay \fR=\fPbool Set TCP_NODELAY on TCP connections. .TP .BI (net,netsplice)protocol \fR=\fPstr "\fR,\fP proto" \fR=\fPstr The network protocol to use. Accepted values are: .RS .RS .TP .B tcp Transmission control protocol .TP .B udp User datagram protocol .TP .B unix UNIX domain socket .RE .P When the protocol is TCP or UDP, the port must also be given, as well as the hostname if the job is a TCP listener or UDP reader. For unix sockets, the normal filename option should be used and the port is invalid. .RE .TP .BI (net,netsplice)listen For TCP network connections, tell fio to listen for incoming connections rather than initiating an outgoing connection. The hostname must be omitted if this option is used. .TP .BI (net, pingpong) \fR=\fPbool Normaly a network writer will just continue writing data, and a network reader will just consume packages. If pingpong=1 is set, a writer will send its normal payload to the reader, then wait for the reader to send the same payload back. This allows fio to measure network latencies. The submission and completion latencies then measure local time spent sending or receiving, and the completion latency measures how long it took for the other end to receive and send back. For UDP multicast traffic pingpong=1 should only be set for a single reader when multiple readers are listening to the same address. .TP .BI (e4defrag,donorname) \fR=\fPstr File will be used as a block donor (swap extents between files) .TP .BI (e4defrag,inplace) \fR=\fPint Configure donor file block allocation strategy .RS .BI 0(default) : Preallocate donor's file on init .TP .BI 1: allocate space immidietly inside defragment event, and free right after event .RE .TP .SH OUTPUT While running, \fBfio\fR will display the status of the created jobs. For example: .RS .P Threads: 1: [_r] [24.8% done] [ 13509/ 8334 kb/s] [eta 00h:01m:31s] .RE .P The characters in the first set of brackets denote the current status of each threads. The possible values are: .P .PD 0 .RS .TP .B P Setup but not started. .TP .B C Thread created. .TP .B I Initialized, waiting. .TP .B R Running, doing sequential reads. .TP .B r Running, doing random reads. .TP .B W Running, doing sequential writes. .TP .B w Running, doing random writes. .TP .B M Running, doing mixed sequential reads/writes. .TP .B m Running, doing mixed random reads/writes. .TP .B F Running, currently waiting for \fBfsync\fR\|(2). .TP .B V Running, verifying written data. .TP .B E Exited, not reaped by main thread. .TP .B \- Exited, thread reaped. .RE .PD .P The second set of brackets shows the estimated completion percentage of the current group. The third set shows the read and write I/O rate, respectively. Finally, the estimated run time of the job is displayed. .P When \fBfio\fR completes (or is interrupted by Ctrl-C), it will show data for each thread, each group of threads, and each disk, in that order. .P Per-thread statistics first show the threads client number, group-id, and error code. The remaining figures are as follows: .RS .TP .B io Number of megabytes of I/O performed. .TP .B bw Average data rate (bandwidth). .TP .B runt Threads run time. .TP .B slat Submission latency minimum, maximum, average and standard deviation. This is the time it took to submit the I/O. .TP .B clat Completion latency minimum, maximum, average and standard deviation. This is the time between submission and completion. .TP .B bw Bandwidth minimum, maximum, percentage of aggregate bandwidth received, average and standard deviation. .TP .B cpu CPU usage statistics. Includes user and system time, number of context switches this thread went through and number of major and minor page faults. .TP .B IO depths Distribution of I/O depths. Each depth includes everything less than (or equal) to it, but greater than the previous depth. .TP .B IO issued Number of read/write requests issued, and number of short read/write requests. .TP .B IO latencies Distribution of I/O completion latencies. The numbers follow the same pattern as \fBIO depths\fR. .RE .P The group statistics show: .PD 0 .RS .TP .B io Number of megabytes I/O performed. .TP .B aggrb Aggregate bandwidth of threads in the group. .TP .B minb Minimum average bandwidth a thread saw. .TP .B maxb Maximum average bandwidth a thread saw. .TP .B mint Shortest runtime of threads in the group. .TP .B maxt Longest runtime of threads in the group. .RE .PD .P Finally, disk statistics are printed with reads first: .PD 0 .RS .TP .B ios Number of I/Os performed by all groups. .TP .B merge Number of merges in the I/O scheduler. .TP .B ticks Number of ticks we kept the disk busy. .TP .B io_queue Total time spent in the disk queue. .TP .B util Disk utilization. .RE .PD .P It is also possible to get fio to dump the current output while it is running, without terminating the job. To do that, send fio the \fBUSR1\fR signal. .SH TERSE OUTPUT If the \fB\-\-minimal\fR option is given, the results will be printed in a semicolon-delimited format suitable for scripted use - a job description (if provided) follows on a new line. Note that the first number in the line is the version number. If the output has to be changed for some reason, this number will be incremented by 1 to signify that change. The fields are: .P .RS .B terse version, fio version, jobname, groupid, error .P Read status: .RS .B Total I/O \fR(KB)\fP, bandwidth \fR(KB/s)\fP, IOPS, runtime \fR(ms)\fP .P Submission latency: .RS .B min, max, mean, standard deviation .RE Completion latency: .RS .B min, max, mean, standard deviation .RE Completion latency percentiles (20 fields): .RS .B Xth percentile=usec .RE Total latency: .RS .B min, max, mean, standard deviation .RE Bandwidth: .RS .B min, max, aggregate percentage of total, mean, standard deviation .RE .RE .P Write status: .RS .B Total I/O \fR(KB)\fP, bandwidth \fR(KB/s)\fP, IOPS, runtime \fR(ms)\fP .P Submission latency: .RS .B min, max, mean, standard deviation .RE Completion latency: .RS .B min, max, mean, standard deviation .RE Completion latency percentiles (20 fields): .RS .B Xth percentile=usec .RE Total latency: .RS .B min, max, mean, standard deviation .RE Bandwidth: .RS .B min, max, aggregate percentage of total, mean, standard deviation .RE .RE .P CPU usage: .RS .B user, system, context switches, major page faults, minor page faults .RE .P IO depth distribution: .RS .B <=1, 2, 4, 8, 16, 32, >=64 .RE .P IO latency distribution: .RS Microseconds: .RS .B <=2, 4, 10, 20, 50, 100, 250, 500, 750, 1000 .RE Milliseconds: .RS .B <=2, 4, 10, 20, 50, 100, 250, 500, 750, 1000, 2000, >=2000 .RE .RE .P Disk utilization (1 for each disk used): .RS .B name, read ios, write ios, read merges, write merges, read ticks, write ticks, read in-queue time, write in-queue time, disk utilization percentage .RE .P Error Info (dependent on continue_on_error, default off): .RS .B total # errors, first error code .RE .P .B text description (if provided in config - appears on newline) .RE .SH CLIENT / SERVER Normally you would run fio as a stand-alone application on the machine where the IO workload should be generated. However, it is also possible to run the frontend and backend of fio separately. This makes it possible to have a fio server running on the machine(s) where the IO workload should be running, while controlling it from another machine. To start the server, you would do: \fBfio \-\-server=args\fR on that machine, where args defines what fio listens to. The arguments are of the form 'type:hostname or IP:port'. 'type' is either 'ip' (or ip4) for TCP/IP v4, 'ip6' for TCP/IP v6, or 'sock' for a local unix domain socket. 'hostname' is either a hostname or IP address, and 'port' is the port to listen to (only valid for TCP/IP, not a local socket). Some examples: 1) fio \-\-server Start a fio server, listening on all interfaces on the default port (8765). 2) fio \-\-server=ip:hostname,4444 Start a fio server, listening on IP belonging to hostname and on port 4444. 3) fio \-\-server=ip6:::1,4444 Start a fio server, listening on IPv6 localhost ::1 and on port 4444. 4) fio \-\-server=,4444 Start a fio server, listening on all interfaces on port 4444. 5) fio \-\-server=1.2.3.4 Start a fio server, listening on IP 1.2.3.4 on the default port. 6) fio \-\-server=sock:/tmp/fio.sock Start a fio server, listening on the local socket /tmp/fio.sock. When a server is running, you can connect to it from a client. The client is run with: fio \-\-local-args \-\-client=server \-\-remote-args where \-\-local-args are arguments that are local to the client where it is running, 'server' is the connect string, and \-\-remote-args and are sent to the server. The 'server' string follows the same format as it does on the server side, to allow IP/hostname/socket and port strings. You can connect to multiple clients as well, to do that you could run: fio \-\-client=server2 \-\-client=server2 .SH AUTHORS .B fio was written by Jens Axboe , now Jens Axboe . .br This man page was written by Aaron Carroll based on documentation by Jens Axboe. .SH "REPORTING BUGS" Report bugs to the \fBfio\fR mailing list . See \fBREADME\fR. .SH "SEE ALSO" For further documentation see \fBHOWTO\fR and \fBREADME\fR. .br Sample jobfiles are available in the \fBexamples\fR directory. fio-2.1.3/fio.c000066400000000000000000000026211222032232000132000ustar00rootroot00000000000000/* * fio - the flexible io tester * * Copyright (C) 2005 Jens Axboe * Copyright (C) 2006-2012 Jens Axboe * * The license below covers all files distributed with fio unless otherwise * noted in the file itself. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include "fio.h" #include "smalloc.h" int main(int argc, char *argv[], char *envp[]) { if (initialize_fio(envp)) return 1; #if !defined(CONFIG_GETTIMEOFDAY) && !defined(CONFIG_CLOCK_GETTIME) #error "No available clock source!" #endif if (parse_options(argc, argv)) return 1; fio_time_init(); if (nr_clients) { if (fio_start_all_clients()) return 1; return fio_handle_clients(&fio_client_ops); } else return fio_backend(); } fio-2.1.3/fio.h000066400000000000000000000323171222032232000132120ustar00rootroot00000000000000#ifndef FIO_H #define FIO_H #include #include #include #include #include #include #include #include #include #include #include #include #include "compiler/compiler.h" #include "thread_options.h" #include "flist.h" #include "fifo.h" #include "arch/arch.h" #include "os/os.h" #include "mutex.h" #include "log.h" #include "debug.h" #include "file.h" #include "io_ddir.h" #include "ioengine.h" #include "iolog.h" #include "helpers.h" #include "options.h" #include "profile.h" #include "time.h" #include "gettime.h" #include "lib/getopt.h" #include "lib/rand.h" #include "lib/rbtree.h" #include "client.h" #include "server.h" #include "stat.h" #include "flow.h" #include "io_u_queue.h" #ifdef CONFIG_SOLARISAIO #include #endif #ifdef CONFIG_LIBNUMA #include #include /* * "local" is pseudo-policy */ #define MPOL_LOCAL MPOL_MAX #endif /* * offset generator types */ enum { RW_SEQ_SEQ = 0, RW_SEQ_IDENT, }; enum { TD_F_VER_BACKLOG = 1, TD_F_TRIM_BACKLOG = 2, TD_F_READ_IOLOG = 4, TD_F_REFILL_BUFFERS = 8, TD_F_SCRAMBLE_BUFFERS = 16, TD_F_VER_NONE = 32, TD_F_PROFILE_OPS = 64, }; enum { FIO_RAND_BS_OFF = 0, FIO_RAND_VER_OFF, FIO_RAND_MIX_OFF, FIO_RAND_FILE_OFF, FIO_RAND_BLOCK_OFF, FIO_RAND_FILE_SIZE_OFF, FIO_RAND_TRIM_OFF, FIO_RAND_BUF_OFF, FIO_RAND_SEQ_RAND_READ_OFF, FIO_RAND_SEQ_RAND_WRITE_OFF, FIO_RAND_SEQ_RAND_TRIM_OFF, FIO_RAND_NR_OFFS, }; /* * This describes a single thread/process executing a fio job. */ struct thread_data { struct thread_options o; unsigned long flags; void *eo; char verror[FIO_VERROR_SIZE]; pthread_t thread; unsigned int thread_number; unsigned int groupid; struct thread_stat ts; int client_type; struct io_log *slat_log; struct io_log *clat_log; struct io_log *lat_log; struct io_log *bw_log; struct io_log *iops_log; uint64_t stat_io_bytes[DDIR_RWDIR_CNT]; struct timeval bw_sample_time; uint64_t stat_io_blocks[DDIR_RWDIR_CNT]; struct timeval iops_sample_time; volatile int update_rusage; struct fio_mutex *rusage_sem; struct rusage ru_start; struct rusage ru_end; struct fio_file **files; unsigned char *file_locks; unsigned int files_size; unsigned int files_index; unsigned int nr_open_files; unsigned int nr_done_files; unsigned int nr_normal_files; union { unsigned int next_file; os_random_state_t next_file_state; struct frand_state __next_file_state; }; int error; int sig; int done; pid_t pid; char *orig_buffer; size_t orig_buffer_size; volatile int terminate; volatile int runstate; unsigned int last_was_sync; enum fio_ddir last_ddir; int mmapfd; void *iolog_buf; FILE *iolog_f; char *sysfs_root; unsigned long rand_seeds[FIO_RAND_NR_OFFS]; union { os_random_state_t bsrange_state; struct frand_state __bsrange_state; }; union { os_random_state_t verify_state; struct frand_state __verify_state; }; union { os_random_state_t trim_state; struct frand_state __trim_state; }; struct frand_state buf_state; unsigned int verify_batch; unsigned int trim_batch; int shm_id; /* * IO engine hooks, contains everything needed to submit an io_u * to any of the available IO engines. */ struct ioengine_ops *io_ops; /* * Queue depth of io_u's that fio MIGHT do */ unsigned int cur_depth; /* * io_u's about to be committed */ unsigned int io_u_queued; /* * io_u's submitted but not completed yet */ unsigned int io_u_in_flight; /* * List of free and busy io_u's */ struct io_u_ring io_u_requeues; struct io_u_queue io_u_freelist; struct io_u_queue io_u_all; pthread_mutex_t io_u_lock; pthread_cond_t free_cond; /* * async verify offload */ struct flist_head verify_list; pthread_t *verify_threads; unsigned int nr_verify_threads; pthread_cond_t verify_cond; int verify_thread_exit; /* * Rate state */ uint64_t rate_bps[DDIR_RWDIR_CNT]; long rate_pending_usleep[DDIR_RWDIR_CNT]; unsigned long rate_bytes[DDIR_RWDIR_CNT]; unsigned long rate_blocks[DDIR_RWDIR_CNT]; struct timeval lastrate[DDIR_RWDIR_CNT]; uint64_t total_io_size; uint64_t fill_device_size; unsigned long io_issues[DDIR_RWDIR_CNT]; uint64_t io_blocks[DDIR_RWDIR_CNT]; uint64_t this_io_blocks[DDIR_RWDIR_CNT]; uint64_t io_bytes[DDIR_RWDIR_CNT]; uint64_t io_skip_bytes; uint64_t this_io_bytes[DDIR_RWDIR_CNT]; uint64_t zone_bytes; struct fio_mutex *mutex; /* * State for random io, a bitmap of blocks done vs not done */ union { os_random_state_t random_state; struct frand_state __random_state; }; struct timeval start; /* start of this loop */ struct timeval epoch; /* time job was started */ struct timeval last_issue; struct timeval tv_cache; unsigned int tv_cache_nr; unsigned int tv_cache_mask; unsigned int ramp_time_over; /* * read/write mixed workload state */ union { os_random_state_t rwmix_state; struct frand_state __rwmix_state; }; unsigned long rwmix_issues; enum fio_ddir rwmix_ddir; unsigned int ddir_seq_nr; /* * rand/seq mixed workload state */ union { os_random_state_t seq_rand_state[DDIR_RWDIR_CNT]; struct frand_state __seq_rand_state[DDIR_RWDIR_CNT]; }; /* * IO history logs for verification. We use a tree for sorting, * if we are overwriting. Otherwise just use a fifo. */ struct rb_root io_hist_tree; struct flist_head io_hist_list; unsigned long io_hist_len; /* * For IO replaying */ struct flist_head io_log_list; /* * For tracking/handling discards */ struct flist_head trim_list; unsigned long trim_entries; struct flist_head next_rand_list; /* * for fileservice, how often to switch to a new file */ unsigned int file_service_nr; unsigned int file_service_left; struct fio_file *file_service_file; unsigned int sync_file_range_nr; /* * For generating file sizes */ union { os_random_state_t file_size_state; struct frand_state __file_size_state; }; /* * Error counts */ unsigned int total_err_count; int first_error; struct fio_flow *flow; /* * Can be overloaded by profiles */ struct prof_io_ops prof_io_ops; void *prof_data; void *pinned_mem; }; /* * when should interactive ETA output be generated */ enum { FIO_ETA_AUTO, FIO_ETA_ALWAYS, FIO_ETA_NEVER, }; #define __td_verror(td, err, msg, func) \ do { \ int e = (err); \ if ((td)->error) \ break; \ (td)->error = e; \ if (!(td)->first_error) \ snprintf(td->verror, sizeof(td->verror), "file:%s:%d, func=%s, error=%s", __FILE__, __LINE__, (func), (msg)); \ } while (0) #define td_clear_error(td) \ (td)->error = 0; #define td_verror(td, err, func) \ __td_verror((td), (err), strerror((err)), (func)) #define td_vmsg(td, err, msg, func) \ __td_verror((td), (err), (msg), (func)) #define __fio_stringify_1(x) #x #define __fio_stringify(x) __fio_stringify_1(x) extern int exitall_on_terminate; extern unsigned int thread_number; extern unsigned int stat_number; extern int shm_id; extern int groupid; extern int output_format; extern int temp_stall_ts; extern uintptr_t page_mask, page_size; extern int read_only; extern int eta_print; extern int eta_new_line; extern unsigned long done_secs; extern char *job_section; extern int fio_gtod_offload; extern int fio_gtod_cpu; extern enum fio_cs fio_clock_source; extern int fio_clock_source_set; extern int warnings_fatal; extern int terse_version; extern int is_backend; extern int nr_clients; extern int log_syslog; extern int status_interval; extern const char fio_version_string[]; extern struct thread_data *threads; static inline void fio_ro_check(struct thread_data *td, struct io_u *io_u) { assert(!(io_u->ddir == DDIR_WRITE && !td_write(td))); } #define REAL_MAX_JOBS 2048 static inline int should_fsync(struct thread_data *td) { if (td->last_was_sync) return 0; if (td_write(td) || td_rw(td) || td->o.override_sync) return 1; return 0; } /* * Init/option functions */ extern int __must_check fio_init_options(void); extern int __must_check parse_options(int, char **); extern int parse_jobs_ini(char *, int, int, int); extern int parse_cmd_line(int, char **, int); extern int fio_backend(void); extern void reset_fio_state(void); extern void clear_io_state(struct thread_data *); extern int fio_options_parse(struct thread_data *, char **, int); extern void fio_keywords_init(void); extern int fio_cmd_option_parse(struct thread_data *, const char *, char *); extern int fio_cmd_ioengine_option_parse(struct thread_data *, const char *, char *); extern void fio_fill_default_options(struct thread_data *); extern int fio_show_option_help(const char *); extern void fio_options_set_ioengine_opts(struct option *long_options, struct thread_data *td); extern void fio_options_dup_and_init(struct option *); extern void fio_options_mem_dupe(struct thread_data *); extern void options_mem_dupe(void *data, struct fio_option *options); extern void td_fill_rand_seeds(struct thread_data *); extern void add_job_opts(const char **, int); extern char *num2str(unsigned long, int, int, int, int); extern int ioengine_load(struct thread_data *); extern int parse_dryrun(void); extern uintptr_t page_mask; extern uintptr_t page_size; extern int initialize_fio(char *envp[]); #define FIO_GETOPT_JOB 0x89000000 #define FIO_GETOPT_IOENGINE 0x98000000 #define FIO_NR_OPTIONS (FIO_MAX_OPTS + 128) /* * ETA/status stuff */ extern void print_thread_status(void); extern void print_status_init(int); extern char *fio_uint_to_kmg(unsigned int val); /* * Thread life cycle. Once a thread has a runstate beyond TD_INITIALIZED, it * will never back again. It may cycle between running/verififying/fsyncing. * Once the thread reaches TD_EXITED, it is just waiting for the core to * reap it. */ enum { TD_NOT_CREATED = 0, TD_CREATED, TD_INITIALIZED, TD_RAMP, TD_SETTING_UP, TD_RUNNING, TD_PRE_READING, TD_VERIFYING, TD_FSYNCING, TD_EXITED, TD_REAPED, }; extern void td_set_runstate(struct thread_data *, int); #define TERMINATE_ALL (-1) extern void fio_terminate_threads(int); /* * Memory helpers */ extern int __must_check fio_pin_memory(struct thread_data *); extern void fio_unpin_memory(struct thread_data *); extern int __must_check allocate_io_mem(struct thread_data *); extern void free_io_mem(struct thread_data *); extern void free_threads_shm(void); /* * Reset stats after ramp time completes */ extern void reset_all_stats(struct thread_data *); /* * blktrace support */ #ifdef FIO_HAVE_BLKTRACE extern int is_blktrace(const char *); extern int load_blktrace(struct thread_data *, const char *); #endif #define for_each_td(td, i) \ for ((i) = 0, (td) = &threads[0]; (i) < (int) thread_number; (i)++, (td)++) #define for_each_file(td, f, i) \ if ((td)->files_index) \ for ((i) = 0, (f) = (td)->files[0]; \ (i) < (td)->o.nr_files && ((f) = (td)->files[i]) != NULL; \ (i)++) #define fio_assert(td, cond) do { \ if (!(cond)) { \ int *__foo = NULL; \ fprintf(stderr, "file:%s:%d, assert %s failed\n", __FILE__, __LINE__, #cond); \ td_set_runstate((td), TD_EXITED); \ (td)->error = EFAULT; \ *__foo = 0; \ } \ } while (0) static inline int fio_fill_issue_time(struct thread_data *td) { if (td->o.read_iolog_file || !td->o.disable_clat || !td->o.disable_slat || !td->o.disable_bw) return 1; return 0; } static inline int __should_check_rate(struct thread_data *td, enum fio_ddir ddir) { struct thread_options *o = &td->o; /* * If some rate setting was given, we need to check it */ if (o->rate[ddir] || o->ratemin[ddir] || o->rate_iops[ddir] || o->rate_iops_min[ddir]) return 1; return 0; } static inline int should_check_rate(struct thread_data *td, uint64_t *bytes_done) { int ret = 0; if (bytes_done[DDIR_READ]) ret |= __should_check_rate(td, DDIR_READ); if (bytes_done[DDIR_WRITE]) ret |= __should_check_rate(td, DDIR_WRITE); if (bytes_done[DDIR_TRIM]) ret |= __should_check_rate(td, DDIR_TRIM); return ret; } static inline unsigned int td_max_bs(struct thread_data *td) { unsigned int max_bs; max_bs = max(td->o.max_bs[DDIR_READ], td->o.max_bs[DDIR_WRITE]); return max(td->o.max_bs[DDIR_TRIM], max_bs); } static inline unsigned int td_min_bs(struct thread_data *td) { unsigned int min_bs; min_bs = min(td->o.min_bs[DDIR_READ], td->o.min_bs[DDIR_WRITE]); return min(td->o.min_bs[DDIR_TRIM], min_bs); } static inline int is_power_of_2(unsigned int val) { return (val != 0 && ((val & (val - 1)) == 0)); } /* * We currently only need to do locking if we have verifier threads * accessing our internal structures too */ static inline void td_io_u_lock(struct thread_data *td) { if (td->o.verify_async) pthread_mutex_lock(&td->io_u_lock); } static inline void td_io_u_unlock(struct thread_data *td) { if (td->o.verify_async) pthread_mutex_unlock(&td->io_u_lock); } static inline void td_io_u_free_notify(struct thread_data *td) { if (td->o.verify_async) pthread_cond_signal(&td->free_cond); } extern const char *fio_get_arch_string(int); extern const char *fio_get_os_string(int); #define ARRAY_SIZE(x) (sizeof((x)) / (sizeof((x)[0]))) enum { FIO_OUTPUT_TERSE = 0, FIO_OUTPUT_JSON, FIO_OUTPUT_NORMAL, }; enum { FIO_RAND_DIST_RANDOM = 0, FIO_RAND_DIST_ZIPF, FIO_RAND_DIST_PARETO, }; enum { FIO_RAND_GEN_TAUSWORTHE = 0, FIO_RAND_GEN_LFSR, }; #endif fio-2.1.3/flist.h000066400000000000000000000111521222032232000135500ustar00rootroot00000000000000#ifndef _LINUX_FLIST_H #define _LINUX_FLIST_H #include #undef offsetof #ifdef __compiler_offsetof #define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER) #else #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) /* * Simple doubly linked list implementation. * * Some of the internal functions ("__xxx") are useful when * manipulating whole lists rather than single entries, as * sometimes we already know the next/prev entries and we can * generate better code by using them directly rather than * using the generic single-entry routines. */ struct flist_head { struct flist_head *next, *prev; }; #define FLIST_HEAD_INIT(name) { &(name), &(name) } #define FLIST_HEAD(name) \ struct flist_head name = FLIST_HEAD_INIT(name) #define INIT_FLIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) /* * Insert a new entry between two known consecutive entries. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __flist_add(struct flist_head *new_entry, struct flist_head *prev, struct flist_head *next) { next->prev = new_entry; new_entry->next = next; new_entry->prev = prev; prev->next = new_entry; } /** * flist_add - add a new entry * @new_entry: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ static inline void flist_add(struct flist_head *new_entry, struct flist_head *head) { __flist_add(new_entry, head, head->next); } static inline void flist_add_tail(struct flist_head *new_entry, struct flist_head *head) { __flist_add(new_entry, head->prev, head); } /* * Delete a list entry by making the prev/next entries * point to each other. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __flist_del(struct flist_head *prev, struct flist_head * next) { next->prev = prev; prev->next = next; } /** * flist_del - deletes entry from list. * @entry: the element to delete from the list. * Note: flist_empty on entry does not return true after this, the entry is * in an undefined state. */ static inline void flist_del(struct flist_head *entry) { __flist_del(entry->prev, entry->next); entry->next = NULL; entry->prev = NULL; } /** * flist_del_init - deletes entry from list and reinitialize it. * @entry: the element to delete from the list. */ static inline void flist_del_init(struct flist_head *entry) { __flist_del(entry->prev, entry->next); INIT_FLIST_HEAD(entry); } /** * flist_empty - tests whether a list is empty * @head: the list to test. */ static inline int flist_empty(const struct flist_head *head) { return head->next == head; } static inline void __flist_splice(const struct flist_head *list, struct flist_head *prev, struct flist_head *next) { struct flist_head *first = list->next; struct flist_head *last = list->prev; first->prev = prev; prev->next = first; last->next = next; next->prev = last; } static inline void flist_splice(const struct flist_head *list, struct flist_head *head) { if (!flist_empty(list)) __flist_splice(list, head, head->next); } static inline void flist_splice_init(struct flist_head *list, struct flist_head *head) { if (!flist_empty(list)) { __flist_splice(list, head, head->next); INIT_FLIST_HEAD(list); } } /** * flist_entry - get the struct for this entry * @ptr: the &struct flist_head pointer. * @type: the type of the struct this is embedded in. * @member: the name of the flist_struct within the struct. */ #define flist_entry(ptr, type, member) \ container_of(ptr, type, member) /** * flist_for_each - iterate over a list * @pos: the &struct flist_head to use as a loop counter. * @head: the head for your list. */ #define flist_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) /** * flist_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct flist_head to use as a loop counter. * @n: another &struct flist_head to use as temporary storage * @head: the head for your list. */ #define flist_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) extern void flist_sort(void *priv, struct flist_head *head, int (*cmp)(void *priv, struct flist_head *a, struct flist_head *b)); #endif fio-2.1.3/flow.c000066400000000000000000000042321222032232000133720ustar00rootroot00000000000000#include "fio.h" #include "mutex.h" #include "smalloc.h" #include "flist.h" struct fio_flow { unsigned int refs; struct flist_head list; unsigned int id; long long int flow_counter; }; static struct flist_head *flow_list; static struct fio_mutex *flow_lock; int flow_threshold_exceeded(struct thread_data *td) { struct fio_flow *flow = td->flow; int sign; if (!flow) return 0; sign = td->o.flow > 0 ? 1 : -1; if (sign * flow->flow_counter > td->o.flow_watermark) { if (td->o.flow_sleep) { io_u_quiesce(td); usleep(td->o.flow_sleep); } return 1; } /* No synchronization needed because it doesn't * matter if the flow count is slightly inaccurate */ flow->flow_counter += td->o.flow; return 0; } static struct fio_flow *flow_get(unsigned int id) { struct fio_flow *flow = NULL; struct flist_head *n; if (!flow_lock) return NULL; fio_mutex_down(flow_lock); flist_for_each(n, flow_list) { flow = flist_entry(n, struct fio_flow, list); if (flow->id == id) break; flow = NULL; } if (!flow) { flow = smalloc(sizeof(*flow)); if (!flow) { log_err("fio: smalloc pool exhausted\n"); return NULL; } flow->refs = 0; INIT_FLIST_HEAD(&flow->list); flow->id = id; flow->flow_counter = 0; flist_add_tail(&flow->list, flow_list); } flow->refs++; fio_mutex_up(flow_lock); return flow; } static void flow_put(struct fio_flow *flow) { if (!flow_lock) return; fio_mutex_down(flow_lock); if (!--flow->refs) { flist_del(&flow->list); sfree(flow); } fio_mutex_up(flow_lock); } void flow_init_job(struct thread_data *td) { if (td->o.flow) td->flow = flow_get(td->o.flow_id); } void flow_exit_job(struct thread_data *td) { if (td->flow) { flow_put(td->flow); td->flow = NULL; } } void flow_init(void) { flow_list = smalloc(sizeof(*flow_list)); if (!flow_list) { log_err("fio: smalloc pool exhausted\n"); return; } flow_lock = fio_mutex_init(FIO_MUTEX_UNLOCKED); if (!flow_lock) { log_err("fio: failed to allocate flow lock\n"); sfree(flow_list); return; } INIT_FLIST_HEAD(flow_list); } void flow_exit(void) { if (flow_lock) fio_mutex_remove(flow_lock); if (flow_list) sfree(flow_list); } fio-2.1.3/flow.h000066400000000000000000000003511222032232000133750ustar00rootroot00000000000000#ifndef FIO_FLOW_H #define FIO_FLOW_H int flow_threshold_exceeded(struct thread_data *td); void flow_init_job(struct thread_data *td); void flow_exit_job(struct thread_data *td); void flow_exit(void); void flow_init(void); #endif fio-2.1.3/gclient.c000066400000000000000000001200501222032232000140450ustar00rootroot00000000000000#include #include #include #include #include #include "fio.h" #include "gfio.h" #include "ghelpers.h" #include "goptions.h" #include "gerror.h" #include "graph.h" #include "gclient.h" #include "printing.h" static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts, struct group_run_stats *rs); static gboolean results_window_delete(GtkWidget *w, gpointer data) { struct gui_entry *ge = (struct gui_entry *) data; gtk_widget_destroy(w); ge->results_window = NULL; ge->results_notebook = NULL; return TRUE; } static void results_close(GtkWidget *w, gpointer *data) { struct gui_entry *ge = (struct gui_entry *) data; gtk_widget_destroy(ge->results_window); } static void results_print(GtkWidget *w, gpointer *data) { struct gui_entry *ge = (struct gui_entry *) data; gfio_print_results(ge); } static GtkActionEntry results_menu_items[] = { { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL}, { "GraphMenuAction", GTK_STOCK_FILE, "Graph", NULL, NULL, NULL}, { "PrintFile", GTK_STOCK_PRINT, "Print", "P", NULL, G_CALLBACK(results_print) }, { "CloseFile", GTK_STOCK_CLOSE, "Close", "W", NULL, G_CALLBACK(results_close) }, }; static gint results_nmenu_items = sizeof(results_menu_items) / sizeof(results_menu_items[0]); static const gchar *results_ui_string = " \ \ \ \ \ \ \ \ \ \ \ "; static GtkWidget *get_results_menubar(GtkWidget *window, struct gui_entry *ge) { GtkActionGroup *action_group; GtkWidget *widget; GError *error = 0; ge->results_uimanager = gtk_ui_manager_new(); action_group = gtk_action_group_new("ResultsMenu"); gtk_action_group_add_actions(action_group, results_menu_items, results_nmenu_items, ge); gtk_ui_manager_insert_action_group(ge->results_uimanager, action_group, 0); gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ge->results_uimanager), results_ui_string, -1, &error); gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ge->results_uimanager)); widget = gtk_ui_manager_get_widget(ge->results_uimanager, "/MainMenu"); return widget; } static GtkWidget *get_results_window(struct gui_entry *ge) { GtkWidget *win, *notebook, *vbox; if (ge->results_window) return ge->results_notebook; win = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(win), "Results"); gtk_window_set_default_size(GTK_WINDOW(win), 1024, 768); g_signal_connect(win, "delete-event", G_CALLBACK(results_window_delete), ge); g_signal_connect(win, "destroy", G_CALLBACK(results_window_delete), ge); vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(win), vbox); ge->results_menu = get_results_menubar(win, ge); gtk_box_pack_start(GTK_BOX(vbox), ge->results_menu, FALSE, FALSE, 0); notebook = gtk_notebook_new(); gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1); gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook)); gtk_container_add(GTK_CONTAINER(vbox), notebook); ge->results_window = win; ge->results_notebook = notebook; return ge->results_notebook; } static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd) { struct cmd_text_pdu *p = (struct cmd_text_pdu *) cmd->payload; struct gfio_client *gc = client->client_data; struct gui_entry *ge = gc->ge; struct gui *ui = ge->ui; GtkTreeIter iter; struct tm *tm; time_t sec; char tmp[64], timebuf[80]; sec = p->log_sec; tm = localtime(&sec); strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", tm); sprintf(timebuf, "%s.%03ld", tmp, (long) p->log_usec / 1000); gdk_threads_enter(); gtk_list_store_append(ui->log_model, &iter); gtk_list_store_set(ui->log_model, &iter, 0, timebuf, -1); gtk_list_store_set(ui->log_model, &iter, 1, client->hostname, -1); gtk_list_store_set(ui->log_model, &iter, 2, log_get_level(p->level), -1); gtk_list_store_set(ui->log_model, &iter, 3, p->buf, -1); if (p->level == FIO_LOG_ERR) gfio_view_log(ui); gdk_threads_leave(); } static void disk_util_destroy(GtkWidget *w, gpointer data) { struct gui_entry *ge = (struct gui_entry *) data; ge->disk_util_vbox = NULL; gtk_widget_destroy(w); } static GtkWidget *gfio_disk_util_get_vbox(struct gui_entry *ge) { GtkWidget *vbox, *box, *scroll, *res_notebook; if (ge->disk_util_vbox) return ge->disk_util_vbox; scroll = get_scrolled_window(5); vbox = gtk_vbox_new(FALSE, 3); box = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox); res_notebook = get_results_window(ge); gtk_notebook_append_page(GTK_NOTEBOOK(res_notebook), scroll, gtk_label_new("Disk utilization")); ge->disk_util_vbox = box; g_signal_connect(vbox, "destroy", G_CALLBACK(disk_util_destroy), ge); return ge->disk_util_vbox; } static int __gfio_disk_util_show(GtkWidget *res_notebook, struct gfio_client *gc, struct cmd_du_pdu *p) { GtkWidget *box, *frame, *entry, *vbox, *util_vbox; struct gui_entry *ge = gc->ge; double util; char tmp[16]; util_vbox = gfio_disk_util_get_vbox(ge); vbox = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(util_vbox), vbox); frame = gtk_frame_new((char *) p->dus.name); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 2); box = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), box); frame = gtk_frame_new("Read"); gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2); vbox = gtk_hbox_new(TRUE, 3); gtk_container_add(GTK_CONTAINER(frame), vbox); entry = new_info_entry_in_frame(vbox, "IOs"); entry_set_int_value(entry, p->dus.ios[0]); entry = new_info_entry_in_frame(vbox, "Merges"); entry_set_int_value(entry, p->dus.merges[0]); entry = new_info_entry_in_frame(vbox, "Sectors"); entry_set_int_value(entry, p->dus.sectors[0]); entry = new_info_entry_in_frame(vbox, "Ticks"); entry_set_int_value(entry, p->dus.ticks[0]); frame = gtk_frame_new("Write"); gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2); vbox = gtk_hbox_new(TRUE, 3); gtk_container_add(GTK_CONTAINER(frame), vbox); entry = new_info_entry_in_frame(vbox, "IOs"); entry_set_int_value(entry, p->dus.ios[1]); entry = new_info_entry_in_frame(vbox, "Merges"); entry_set_int_value(entry, p->dus.merges[1]); entry = new_info_entry_in_frame(vbox, "Sectors"); entry_set_int_value(entry, p->dus.sectors[1]); entry = new_info_entry_in_frame(vbox, "Ticks"); entry_set_int_value(entry, p->dus.ticks[1]); frame = gtk_frame_new("Shared"); gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 2); vbox = gtk_hbox_new(TRUE, 3); gtk_container_add(GTK_CONTAINER(frame), vbox); entry = new_info_entry_in_frame(vbox, "IO ticks"); entry_set_int_value(entry, p->dus.io_ticks); entry = new_info_entry_in_frame(vbox, "Time in queue"); entry_set_int_value(entry, p->dus.time_in_queue); util = 0.0; if (p->dus.msec) util = (double) 100 * p->dus.io_ticks / (double) p->dus.msec; if (util > 100.0) util = 100.0; sprintf(tmp, "%3.2f%%", util); entry = new_info_entry_in_frame(vbox, "Disk utilization"); gtk_entry_set_text(GTK_ENTRY(entry), tmp); gtk_widget_show_all(ge->results_window); return 0; } static int gfio_disk_util_show(struct gfio_client *gc) { struct gui_entry *ge = gc->ge; GtkWidget *res_notebook; int i; if (!gc->nr_du) return 1; res_notebook = get_results_window(ge); for (i = 0; i < gc->nr_du; i++) { struct cmd_du_pdu *p = &gc->du[i]; __gfio_disk_util_show(res_notebook, gc, p); } gtk_widget_show_all(ge->results_window); return 0; } static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd) { struct cmd_du_pdu *p = (struct cmd_du_pdu *) cmd->payload; struct gfio_client *gc = client->client_data; struct gui_entry *ge = gc->ge; unsigned int nr = gc->nr_du; gc->du = realloc(gc->du, (nr + 1) * sizeof(struct cmd_du_pdu)); memcpy(&gc->du[nr], p, sizeof(*p)); gc->nr_du++; gdk_threads_enter(); if (ge->results_window) __gfio_disk_util_show(ge->results_notebook, gc, p); else gfio_disk_util_show(gc); gdk_threads_leave(); } extern int sum_stat_clients; extern struct thread_stat client_ts; extern struct group_run_stats client_gs; static int sum_stat_nr; static void gfio_thread_status_op(struct fio_client *client, struct fio_net_cmd *cmd) { struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload; gfio_display_ts(client, &p->ts, &p->rs); if (sum_stat_clients == 1) return; sum_thread_stats(&client_ts, &p->ts, sum_stat_nr); sum_group_stats(&client_gs, &p->rs); client_ts.members++; client_ts.thread_number = p->ts.thread_number; client_ts.groupid = p->ts.groupid; if (++sum_stat_nr == sum_stat_clients) { strcpy(client_ts.name, "All clients"); gfio_display_ts(client, &client_ts, &client_gs); } } static void gfio_group_stats_op(struct fio_client *client, struct fio_net_cmd *cmd) { /* We're ignoring group stats for now */ } static void gfio_update_thread_status(struct gui_entry *ge, char *status_message, double perc) { static char message[100]; const char *m = message; strncpy(message, status_message, sizeof(message) - 1); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), m); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), perc / 100.0); gtk_widget_queue_draw(ge->ui->window); } static void gfio_update_thread_status_all(struct gui *ui, char *status_message, double perc) { static char message[100]; const char *m = message; strncpy(message, status_message, sizeof(message) - 1); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), m); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), perc / 100.0); gtk_widget_queue_draw(ui->window); } /* * Client specific ETA */ static void gfio_update_client_eta(struct fio_client *client, struct jobs_eta *je) { struct gfio_client *gc = client->client_data; struct gui_entry *ge = gc->ge; static int eta_good; char eta_str[128]; char output[256]; char tmp[32]; double perc = 0.0; int i2p = 0; gdk_threads_enter(); eta_str[0] = '\0'; output[0] = '\0'; if (je->eta_sec != INT_MAX && je->elapsed_sec) { perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec); eta_to_str(eta_str, je->eta_sec); } sprintf(tmp, "%u", je->nr_running); gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), tmp); sprintf(tmp, "%u", je->files_open); gtk_entry_set_text(GTK_ENTRY(ge->eta.files), tmp); #if 0 if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) { if (je->m_rate || je->t_rate) { char *tr, *mr; mr = num2str(je->m_rate, 4, 0, i2p); tr = num2str(je->t_rate, 4, 0, i2p); gtk_entry_set_text(GTK_ENTRY(ge->eta); p += sprintf(p, ", CR=%s/%s KB/s", tr, mr); free(tr); free(mr); } else if (je->m_iops || je->t_iops) p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops); gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_bw), "---"); gtk_entry_set_text(GTK_ENTRY(ge->eta.cr_iops), "---"); gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_bw), "---"); gtk_entry_set_text(GTK_ENTRY(ge->eta.cw_iops), "---"); #endif if (je->eta_sec != INT_MAX && je->nr_running) { char *iops_str[DDIR_RWDIR_CNT]; char *rate_str[DDIR_RWDIR_CNT]; int i; if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running) strcpy(output, "-.-% done"); else { eta_good = 1; perc *= 100.0; sprintf(output, "%3.1f%% done", perc); } rate_str[0] = num2str(je->rate[0], 5, 10, i2p, 0); rate_str[1] = num2str(je->rate[1], 5, 10, i2p, 0); rate_str[2] = num2str(je->rate[2], 5, 10, i2p, 0); iops_str[0] = num2str(je->iops[0], 4, 1, 0, 0); iops_str[1] = num2str(je->iops[1], 4, 1, 0, 0); iops_str[2] = num2str(je->iops[2], 4, 1, 0, 0); gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), rate_str[0]); gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), iops_str[0]); gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), rate_str[1]); gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), iops_str[1]); gtk_entry_set_text(GTK_ENTRY(ge->eta.trim_bw), rate_str[2]); gtk_entry_set_text(GTK_ENTRY(ge->eta.trim_iops), iops_str[2]); graph_add_xy_data(ge->graphs.iops_graph, ge->graphs.read_iops, je->elapsed_sec, je->iops[0], iops_str[0]); graph_add_xy_data(ge->graphs.iops_graph, ge->graphs.write_iops, je->elapsed_sec, je->iops[1], iops_str[1]); graph_add_xy_data(ge->graphs.iops_graph, ge->graphs.trim_iops, je->elapsed_sec, je->iops[2], iops_str[2]); graph_add_xy_data(ge->graphs.bandwidth_graph, ge->graphs.read_bw, je->elapsed_sec, je->rate[0], rate_str[0]); graph_add_xy_data(ge->graphs.bandwidth_graph, ge->graphs.write_bw, je->elapsed_sec, je->rate[1], rate_str[1]); graph_add_xy_data(ge->graphs.bandwidth_graph, ge->graphs.trim_bw, je->elapsed_sec, je->rate[2], rate_str[2]); for (i = 0; i < DDIR_RWDIR_CNT; i++) { free(rate_str[i]); free(iops_str[i]); } } if (eta_str[0]) { char *dst = output + strlen(output); sprintf(dst, " - %s", eta_str); } gfio_update_thread_status(ge, output, perc); gdk_threads_leave(); } /* * Update ETA in main window for all clients */ static void gfio_update_all_eta(struct jobs_eta *je) { struct gui *ui = &main_ui; static int eta_good; char eta_str[128]; char output[256]; double perc = 0.0; int i, i2p = 0; gdk_threads_enter(); eta_str[0] = '\0'; output[0] = '\0'; if (je->eta_sec != INT_MAX && je->elapsed_sec) { perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec); eta_to_str(eta_str, je->eta_sec); } #if 0 if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) { if (je->m_rate || je->t_rate) { char *tr, *mr; mr = num2str(je->m_rate, 4, 0, i2p); tr = num2str(je->t_rate, 4, 0, i2p); gtk_entry_set_text(GTK_ENTRY(ui->eta); p += sprintf(p, ", CR=%s/%s KB/s", tr, mr); free(tr); free(mr); } else if (je->m_iops || je->t_iops) p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops); gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_bw), "---"); gtk_entry_set_text(GTK_ENTRY(ui->eta.cr_iops), "---"); gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_bw), "---"); gtk_entry_set_text(GTK_ENTRY(ui->eta.cw_iops), "---"); #endif entry_set_int_value(ui->eta.jobs, je->nr_running); if (je->eta_sec != INT_MAX && je->nr_running) { char *iops_str[3]; char *rate_str[3]; if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running) strcpy(output, "-.-% done"); else { eta_good = 1; perc *= 100.0; sprintf(output, "%3.1f%% done", perc); } rate_str[0] = num2str(je->rate[0], 5, 10, i2p, 0); rate_str[1] = num2str(je->rate[1], 5, 10, i2p, 0); rate_str[2] = num2str(je->rate[2], 5, 10, i2p, 0); iops_str[0] = num2str(je->iops[0], 4, 1, 0, 0); iops_str[1] = num2str(je->iops[1], 4, 1, 0, 0); iops_str[2] = num2str(je->iops[2], 4, 1, 0, 0); gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), rate_str[0]); gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), iops_str[0]); gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), rate_str[1]); gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), iops_str[1]); gtk_entry_set_text(GTK_ENTRY(ui->eta.trim_bw), rate_str[2]); gtk_entry_set_text(GTK_ENTRY(ui->eta.trim_iops), iops_str[2]); graph_add_xy_data(ui->graphs.iops_graph, ui->graphs.read_iops, je->elapsed_sec, je->iops[0], iops_str[0]); graph_add_xy_data(ui->graphs.iops_graph, ui->graphs.write_iops, je->elapsed_sec, je->iops[1], iops_str[1]); graph_add_xy_data(ui->graphs.iops_graph, ui->graphs.trim_iops, je->elapsed_sec, je->iops[2], iops_str[2]); graph_add_xy_data(ui->graphs.bandwidth_graph, ui->graphs.read_bw, je->elapsed_sec, je->rate[0], rate_str[0]); graph_add_xy_data(ui->graphs.bandwidth_graph, ui->graphs.write_bw, je->elapsed_sec, je->rate[1], rate_str[1]); graph_add_xy_data(ui->graphs.bandwidth_graph, ui->graphs.trim_bw, je->elapsed_sec, je->rate[2], rate_str[2]); for (i = 0; i < DDIR_RWDIR_CNT; i++) { free(rate_str[i]); free(iops_str[i]); } } if (eta_str[0]) { char *dst = output + strlen(output); sprintf(dst, " - %s", eta_str); } gfio_update_thread_status_all(ui, output, perc); gdk_threads_leave(); } static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd) { struct cmd_probe_reply_pdu *probe = (struct cmd_probe_reply_pdu *) cmd->payload; struct gfio_client *gc = client->client_data; struct gui_entry *ge = gc->ge; const char *os, *arch; os = fio_get_os_string(probe->os); if (!os) os = "unknown"; arch = fio_get_arch_string(probe->arch); if (!arch) os = "unknown"; if (!client->name) client->name = strdup((char *) probe->hostname); gc->client_cpus = le32_to_cpu(probe->cpus); gc->client_flags = le64_to_cpu(probe->flags); gdk_threads_enter(); gtk_label_set_text(GTK_LABEL(ge->probe.hostname), (char *) probe->hostname); gtk_label_set_text(GTK_LABEL(ge->probe.os), os); gtk_label_set_text(GTK_LABEL(ge->probe.arch), arch); gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), (char *) probe->fio_version); gfio_set_state(ge, GE_STATE_CONNECTED); gdk_threads_leave(); } static void gfio_quit_op(struct fio_client *client, struct fio_net_cmd *cmd) { struct gfio_client *gc = client->client_data; gdk_threads_enter(); gfio_set_state(gc->ge, GE_STATE_NEW); gdk_threads_leave(); } static struct thread_options *gfio_client_add_job(struct gfio_client *gc, struct thread_options_pack *top) { struct gfio_client_options *gco; gco = calloc(1, sizeof(*gco)); convert_thread_options_to_cpu(&gco->o, top); INIT_FLIST_HEAD(&gco->list); flist_add_tail(&gco->list, &gc->o_list); gc->o_list_nr = 1; return &gco->o; } static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd) { struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload; struct gfio_client *gc = client->client_data; struct gui_entry *ge = gc->ge; struct thread_options *o; char *c1, *c2, *c3, *c4; char tmp[80]; p->thread_number = le32_to_cpu(p->thread_number); p->groupid = le32_to_cpu(p->groupid); o = gfio_client_add_job(gc, &p->top); gdk_threads_enter(); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ge->eta.names), (gchar *) o->name); gtk_combo_box_set_active(GTK_COMBO_BOX(ge->eta.names), 0); sprintf(tmp, "%s %s", o->odirect ? "direct" : "buffered", ddir_str(o->td_ddir)); multitext_add_entry(&ge->eta.iotype, tmp); c1 = fio_uint_to_kmg(o->min_bs[DDIR_READ]); c2 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]); c3 = fio_uint_to_kmg(o->min_bs[DDIR_READ]); c4 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]); sprintf(tmp, "%s-%s/%s-%s", c1, c2, c3, c4); free(c1); free(c2); free(c3); free(c4); multitext_add_entry(&ge->eta.bs, tmp); multitext_add_entry(&ge->eta.ioengine, (const char *) o->ioengine); sprintf(tmp, "%u", o->iodepth); multitext_add_entry(&ge->eta.iodepth, tmp); multitext_set_entry(&ge->eta.iotype, 0); multitext_set_entry(&ge->eta.bs, 0); multitext_set_entry(&ge->eta.ioengine, 0); multitext_set_entry(&ge->eta.iodepth, 0); gfio_set_state(ge, GE_STATE_JOB_SENT); gdk_threads_leave(); } static void gfio_update_job_op(struct fio_client *client, struct fio_net_cmd *cmd) { uint32_t *pdu_error = (uint32_t *) cmd->payload; struct gfio_client *gc = client->client_data; gc->update_job_status = le32_to_cpu(*pdu_error); gc->update_job_done = 1; } static void gfio_client_timed_out(struct fio_client *client) { struct gfio_client *gc = client->client_data; char buf[256]; gdk_threads_enter(); gfio_set_state(gc->ge, GE_STATE_NEW); clear_ge_ui_info(gc->ge); sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname); gfio_report_info(gc->ge->ui, "Network timeout", buf); gdk_threads_leave(); } static void gfio_client_stop(struct fio_client *client, struct fio_net_cmd *cmd) { struct gfio_client *gc = client->client_data; gdk_threads_enter(); gfio_set_state(gc->ge, GE_STATE_JOB_DONE); if (gc->err_entry) entry_set_int_value(gc->err_entry, client->error); gdk_threads_leave(); } static void gfio_client_start(struct fio_client *client, struct fio_net_cmd *cmd) { struct gfio_client *gc = client->client_data; gdk_threads_enter(); gfio_set_state(gc->ge, GE_STATE_JOB_STARTED); gdk_threads_leave(); } static void gfio_client_job_start(struct fio_client *client, struct fio_net_cmd *cmd) { struct gfio_client *gc = client->client_data; gdk_threads_enter(); gfio_set_state(gc->ge, GE_STATE_JOB_RUNNING); gdk_threads_leave(); } static void gfio_client_iolog(struct fio_client *client, struct cmd_iolog_pdu *pdu) { printf("got iolog: name=%s, type=%u, entries=%u\n", pdu->name, pdu->log_type, pdu->nr_samples); free(pdu); } static void gfio_add_total_depths_tree(GtkListStore *model, struct thread_stat *ts, unsigned int len) { double io_u_dist[FIO_IO_U_MAP_NR]; GtkTreeIter iter; /* Bits 1-6, and 8 */ const int add_mask = 0x17e; int i, j; stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist); gtk_list_store_append(model, &iter); gtk_list_store_set(model, &iter, 0, "Total", -1); for (i = 1, j = 0; i < len; i++) { char fbuf[32]; if (!(add_mask & (1UL << (i - 1)))) sprintf(fbuf, "0.0%%"); else { sprintf(fbuf, "%3.1f%%", io_u_dist[j]); j++; } gtk_list_store_set(model, &iter, i, fbuf, -1); } } static void gfio_add_end_results(struct gfio_client *gc, struct thread_stat *ts, struct group_run_stats *rs) { unsigned int nr = gc->nr_results; gc->results = realloc(gc->results, (nr + 1) * sizeof(struct end_results)); memcpy(&gc->results[nr].ts, ts, sizeof(*ts)); memcpy(&gc->results[nr].gs, rs, sizeof(*rs)); gc->nr_results++; } static void gfio_add_sc_depths_tree(GtkListStore *model, struct thread_stat *ts, unsigned int len, int submit) { double io_u_dist[FIO_IO_U_MAP_NR]; GtkTreeIter iter; /* Bits 0, and 3-8 */ const int add_mask = 0x1f9; int i, j; if (submit) stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist); else stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist); gtk_list_store_append(model, &iter); gtk_list_store_set(model, &iter, 0, submit ? "Submit" : "Complete", -1); for (i = 1, j = 0; i < len; i++) { char fbuf[32]; if (!(add_mask & (1UL << (i - 1)))) sprintf(fbuf, "0.0%%"); else { sprintf(fbuf, "%3.1f%%", io_u_dist[j]); j++; } gtk_list_store_set(model, &iter, i, fbuf, -1); } } static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts) { GtkWidget *frame, *box, *tree_view = NULL; GtkTreeSelection *selection; GtkListStore *model; int i; const char *labels[] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" }; const int nr_labels = ARRAY_SIZE(labels); GType types[nr_labels]; frame = gtk_frame_new("IO depths"); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); box = gtk_hbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), box); for (i = 0; i < nr_labels; i++) types[i] = G_TYPE_STRING; model = gtk_list_store_newv(nr_labels, types); tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); gtk_widget_set_can_focus(tree_view, FALSE); g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE, "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE); for (i = 0; i < nr_labels; i++) tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE); gfio_add_total_depths_tree(model, ts, nr_labels); gfio_add_sc_depths_tree(model, ts, nr_labels, 1); gfio_add_sc_depths_tree(model, ts, nr_labels, 0); gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, TRUE, 3); } static void gfio_show_cpu_usage(GtkWidget *vbox, struct thread_stat *ts) { GtkWidget *box, *frame, *entry; double usr_cpu, sys_cpu; unsigned long runtime; char tmp[32]; runtime = ts->total_run_time; if (runtime) { double runt = (double) runtime; usr_cpu = (double) ts->usr_time * 100 / runt; sys_cpu = (double) ts->sys_time * 100 / runt; } else { usr_cpu = 0; sys_cpu = 0; } frame = gtk_frame_new("OS resources"); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); box = gtk_hbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), box); entry = new_info_entry_in_frame(box, "User CPU"); sprintf(tmp, "%3.2f%%", usr_cpu); gtk_entry_set_text(GTK_ENTRY(entry), tmp); entry = new_info_entry_in_frame(box, "System CPU"); sprintf(tmp, "%3.2f%%", sys_cpu); gtk_entry_set_text(GTK_ENTRY(entry), tmp); entry = new_info_entry_in_frame(box, "Context switches"); entry_set_int_value(entry, ts->ctx); entry = new_info_entry_in_frame(box, "Major faults"); entry_set_int_value(entry, ts->majf); entry = new_info_entry_in_frame(box, "Minor faults"); entry_set_int_value(entry, ts->minf); } static GtkWidget *gfio_output_lat_buckets(double *lat, const char **labels, int num) { GtkWidget *tree_view; GtkTreeSelection *selection; GtkListStore *model; GtkTreeIter iter; GType *types; int i; types = malloc(num * sizeof(GType)); for (i = 0; i < num; i++) types[i] = G_TYPE_STRING; model = gtk_list_store_newv(num, types); free(types); types = NULL; tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); gtk_widget_set_can_focus(tree_view, FALSE); g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE, "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE); for (i = 0; i < num; i++) tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE); gtk_list_store_append(model, &iter); for (i = 0; i < num; i++) { char fbuf[32]; if (lat[i] <= 0.0) sprintf(fbuf, "0.00"); else sprintf(fbuf, "%3.2f%%", lat[i]); gtk_list_store_set(model, &iter, i, fbuf, -1); } return tree_view; } static struct graph *setup_lat_bucket_graph(const char *title, double *lat, const char **labels, unsigned int len, double xdim, double ydim) { struct graph *g; int i; g = graph_new(xdim, ydim, gfio_graph_font); graph_title(g, title); graph_x_title(g, "Buckets"); graph_y_title(g, "Percent"); for (i = 0; i < len; i++) { graph_label_t l; l = graph_add_label(g, labels[i]); graph_add_data(g, l, lat[i]); } return g; } static int on_expose_lat_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p) { struct graph *g = p; cairo_t *cr; cr = gdk_cairo_create(gtk_widget_get_window(w)); #if 0 if (graph_has_tooltips(g)) { g_object_set(w, "has-tooltip", TRUE, NULL); g_signal_connect(w, "query-tooltip", G_CALLBACK(clat_graph_tooltip), g); } #endif cairo_set_source_rgb(cr, 0, 0, 0); bar_graph_draw(g, cr); cairo_destroy(cr); return FALSE; } static gint on_config_lat_drawing_area(GtkWidget *w, GdkEventConfigure *event, gpointer data) { guint width = gtk_widget_get_allocated_width(w); guint height = gtk_widget_get_allocated_height(w); struct graph *g = data; graph_set_size(g, width, height); graph_set_size(g, width, height); graph_set_position(g, 0, 0); return TRUE; } static void gfio_show_latency_buckets(struct gfio_client *gc, GtkWidget *vbox, struct thread_stat *ts) { double io_u_lat[FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR]; const char *ranges[] = { "2u", "4u", "10u", "20u", "50u", "100u", "250u", "500u", "750u", "1m", "2m", "4m", "10m", "20m", "50m", "100m", "250m", "500m", "750m", "1s", "2s", ">= 2s" }; int start, end, i; const int total = FIO_IO_U_LAT_U_NR + FIO_IO_U_LAT_M_NR; GtkWidget *frame, *tree_view, *hbox, *completion_vbox, *drawing_area; struct gui_entry *ge = gc->ge; stat_calc_lat_u(ts, io_u_lat); stat_calc_lat_m(ts, &io_u_lat[FIO_IO_U_LAT_U_NR]); /* * Found out which first bucket has entries, and which last bucket */ start = end = -1U; for (i = 0; i < total; i++) { if (io_u_lat[i] == 0.00) continue; if (start == -1U) start = i; end = i; } /* * No entries... */ if (start == -1U) return; tree_view = gfio_output_lat_buckets(&io_u_lat[start], &ranges[start], end - start + 1); ge->lat_bucket_graph = setup_lat_bucket_graph("Latency Buckets", &io_u_lat[start], &ranges[start], end - start + 1, 700.0, 300.0); frame = gtk_frame_new("Latency buckets"); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); completion_vbox = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), completion_vbox); hbox = gtk_hbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(completion_vbox), hbox); drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300); gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &gfio_color_white); gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area); g_signal_connect(G_OBJECT(drawing_area), GFIO_DRAW_EVENT, G_CALLBACK(on_expose_lat_drawing_area), ge->lat_bucket_graph); g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->lat_bucket_graph); gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, TRUE, 3); } static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min, unsigned long max, double mean, double dev) { const char *base = "(usec)"; GtkWidget *hbox, *label, *frame; char *minp, *maxp; char tmp[64]; if (!usec_to_msec(&min, &max, &mean, &dev)) base = "(msec)"; minp = num2str(min, 6, 1, 0, 0); maxp = num2str(max, 6, 1, 0, 0); sprintf(tmp, "%s %s", name, base); frame = gtk_frame_new(tmp); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); hbox = gtk_hbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), hbox); label = new_info_label_in_frame(hbox, "Minimum"); gtk_label_set_text(GTK_LABEL(label), minp); label = new_info_label_in_frame(hbox, "Maximum"); gtk_label_set_text(GTK_LABEL(label), maxp); label = new_info_label_in_frame(hbox, "Average"); sprintf(tmp, "%5.02f", mean); gtk_label_set_text(GTK_LABEL(label), tmp); label = new_info_label_in_frame(hbox, "Standard deviation"); sprintf(tmp, "%5.02f", dev); gtk_label_set_text(GTK_LABEL(label), tmp); free(minp); free(maxp); } static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals, fio_fp64_t *plist, unsigned int len, const char *base, unsigned int scale) { GType types[FIO_IO_U_LIST_MAX_LEN]; GtkWidget *tree_view; GtkTreeSelection *selection; GtkListStore *model; GtkTreeIter iter; int i; for (i = 0; i < len; i++) types[i] = G_TYPE_INT; model = gtk_list_store_newv(len, types); tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); gtk_widget_set_can_focus(tree_view, FALSE); g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE, "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE); for (i = 0; i < len; i++) { char fbuf[8]; sprintf(fbuf, "%2.2f%%", plist[i].u.f); tree_view_column(tree_view, i, fbuf, ALIGN_RIGHT | UNSORTABLE); } gtk_list_store_append(model, &iter); for (i = 0; i < len; i++) { if (scale) ovals[i] = (ovals[i] + 999) / 1000; gtk_list_store_set(model, &iter, i, ovals[i], -1); } return tree_view; } static struct graph *setup_clat_graph(char *title, unsigned int *ovals, fio_fp64_t *plist, unsigned int len, double xdim, double ydim) { struct graph *g; int i; g = graph_new(xdim, ydim, gfio_graph_font); graph_title(g, title); graph_x_title(g, "Percentile"); graph_y_title(g, "Time"); for (i = 0; i < len; i++) { graph_label_t l; char fbuf[8]; sprintf(fbuf, "%2.2f%%", plist[i].u.f); l = graph_add_label(g, fbuf); graph_add_data(g, l, (double) ovals[i]); } return g; } static void gfio_show_clat_percentiles(struct gfio_client *gc, GtkWidget *vbox, struct thread_stat *ts, int ddir) { unsigned int *io_u_plat = ts->io_u_plat[ddir]; unsigned long nr = ts->clat_stat[ddir].samples; fio_fp64_t *plist = ts->percentile_list; unsigned int *ovals, len, minv, maxv, scale_down; const char *base; GtkWidget *tree_view, *frame, *hbox, *drawing_area, *completion_vbox; struct gui_entry *ge = gc->ge; char tmp[64]; len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv); if (!len) goto out; /* * We default to usecs, but if the value range is such that we * should scale down to msecs, do that. */ if (minv > 2000 && maxv > 99999) { scale_down = 1; base = "msec"; } else { scale_down = 0; base = "usec"; } sprintf(tmp, "Completion percentiles (%s)", base); tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down); ge->clat_graph = setup_clat_graph(tmp, ovals, plist, len, 700.0, 300.0); frame = gtk_frame_new(tmp); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); completion_vbox = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), completion_vbox); hbox = gtk_hbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(completion_vbox), hbox); drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(GTK_WIDGET(drawing_area), 700, 300); gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &gfio_color_white); gtk_container_add(GTK_CONTAINER(completion_vbox), drawing_area); g_signal_connect(G_OBJECT(drawing_area), GFIO_DRAW_EVENT, G_CALLBACK(on_expose_lat_drawing_area), ge->clat_graph); g_signal_connect(G_OBJECT(drawing_area), "configure_event", G_CALLBACK(on_config_lat_drawing_area), ge->clat_graph); gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, TRUE, 3); out: if (ovals) free(ovals); } #define GFIO_CLAT 1 #define GFIO_SLAT 2 #define GFIO_LAT 4 static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox, struct group_run_stats *rs, struct thread_stat *ts, int ddir) { const char *ddir_label[3] = { "Read", "Write", "Trim" }; GtkWidget *frame, *label, *box, *vbox, *main_vbox; unsigned long min[3], max[3], runt; unsigned long long bw, iops; unsigned int flags = 0; double mean[3], dev[3]; char *io_p, *bw_p, *iops_p; int i2p; if (!ts->runtime[ddir]) return; i2p = is_power_of_2(rs->kb_base); runt = ts->runtime[ddir]; bw = (1000 * ts->io_bytes[ddir]) / runt; io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p, 8); bw_p = num2str(bw, 6, 1, i2p, ts->unit_base); iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt; iops_p = num2str(iops, 6, 1, 0, 0); box = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3); frame = gtk_frame_new(ddir_label[ddir]); gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 5); main_vbox = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), main_vbox); box = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(main_vbox), box, TRUE, FALSE, 3); label = new_info_label_in_frame(box, "IO"); gtk_label_set_text(GTK_LABEL(label), io_p); label = new_info_label_in_frame(box, "Bandwidth"); gtk_label_set_text(GTK_LABEL(label), bw_p); label = new_info_label_in_frame(box, "IOPS"); gtk_label_set_text(GTK_LABEL(label), iops_p); label = new_info_label_in_frame(box, "Runtime (msec)"); label_set_int_value(label, ts->runtime[ddir]); if (calc_lat(&ts->bw_stat[ddir], &min[0], &max[0], &mean[0], &dev[0])) { double p_of_agg = 100.0; const char *bw_str = "KB"; char tmp[32]; if (rs->agg[ddir]) { p_of_agg = mean[0] * 100 / (double) rs->agg[ddir]; if (p_of_agg > 100.0) p_of_agg = 100.0; } if (mean[0] > 999999.9) { min[0] /= 1000.0; max[0] /= 1000.0; mean[0] /= 1000.0; dev[0] /= 1000.0; bw_str = "MB"; } sprintf(tmp, "Bandwidth (%s)", bw_str); frame = gtk_frame_new(tmp); gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5); box = gtk_hbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), box); label = new_info_label_in_frame(box, "Minimum"); label_set_int_value(label, min[0]); label = new_info_label_in_frame(box, "Maximum"); label_set_int_value(label, max[0]); label = new_info_label_in_frame(box, "Percentage of jobs"); sprintf(tmp, "%3.2f%%", p_of_agg); gtk_label_set_text(GTK_LABEL(label), tmp); label = new_info_label_in_frame(box, "Average"); sprintf(tmp, "%5.02f", mean[0]); gtk_label_set_text(GTK_LABEL(label), tmp); label = new_info_label_in_frame(box, "Standard deviation"); sprintf(tmp, "%5.02f", dev[0]); gtk_label_set_text(GTK_LABEL(label), tmp); } if (calc_lat(&ts->slat_stat[ddir], &min[0], &max[0], &mean[0], &dev[0])) flags |= GFIO_SLAT; if (calc_lat(&ts->clat_stat[ddir], &min[1], &max[1], &mean[1], &dev[1])) flags |= GFIO_CLAT; if (calc_lat(&ts->lat_stat[ddir], &min[2], &max[2], &mean[2], &dev[2])) flags |= GFIO_LAT; if (flags) { frame = gtk_frame_new("Latency"); gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5); vbox = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), vbox); if (flags & GFIO_SLAT) gfio_show_lat(vbox, "Submission latency", min[0], max[0], mean[0], dev[0]); if (flags & GFIO_CLAT) gfio_show_lat(vbox, "Completion latency", min[1], max[1], mean[1], dev[1]); if (flags & GFIO_LAT) gfio_show_lat(vbox, "Total latency", min[2], max[2], mean[2], dev[2]); } if (ts->clat_percentiles) gfio_show_clat_percentiles(gc, main_vbox, ts, ddir); free(io_p); free(bw_p); free(iops_p); } static void __gfio_display_end_results(GtkWidget *win, struct gfio_client *gc, struct thread_stat *ts, struct group_run_stats *rs) { GtkWidget *box, *vbox, *entry, *scroll; int i; scroll = gtk_scrolled_window_new(NULL, NULL); gtk_container_set_border_width(GTK_CONTAINER(scroll), 5); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); vbox = gtk_vbox_new(FALSE, 3); box = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 5); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox); gtk_notebook_append_page(GTK_NOTEBOOK(win), scroll, gtk_label_new(ts->name)); entry = new_info_entry_in_frame(box, "Name"); gtk_entry_set_text(GTK_ENTRY(entry), ts->name); if (strlen(ts->description)) { entry = new_info_entry_in_frame(box, "Description"); gtk_entry_set_text(GTK_ENTRY(entry), ts->description); } entry = new_info_entry_in_frame(box, "Group ID"); entry_set_int_value(entry, ts->groupid); entry = new_info_entry_in_frame(box, "Jobs"); entry_set_int_value(entry, ts->members); gc->err_entry = entry = new_info_entry_in_frame(box, "Error"); entry_set_int_value(entry, ts->error); entry = new_info_entry_in_frame(box, "PID"); entry_set_int_value(entry, ts->pid); for (i = 0; i < DDIR_RWDIR_CNT; i++) { if (ts->io_bytes[i]) gfio_show_ddir_status(gc, vbox, rs, ts, i); } gfio_show_latency_buckets(gc, vbox, ts); gfio_show_cpu_usage(vbox, ts); gfio_show_io_depths(vbox, ts); } void gfio_display_end_results(struct gfio_client *gc) { struct gui_entry *ge = gc->ge; GtkWidget *res_notebook; int i; res_notebook = get_results_window(ge); for (i = 0; i < gc->nr_results; i++) { struct end_results *e = &gc->results[i]; __gfio_display_end_results(res_notebook, gc, &e->ts, &e->gs); } if (gfio_disk_util_show(gc)) gtk_widget_show_all(ge->results_window); } static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts, struct group_run_stats *rs) { struct gfio_client *gc = client->client_data; struct gui_entry *ge = gc->ge; gfio_add_end_results(gc, ts, rs); gdk_threads_enter(); if (ge->results_window) __gfio_display_end_results(ge->results_notebook, gc, ts, rs); else gfio_display_end_results(gc); gdk_threads_leave(); } static void gfio_client_removed(struct fio_client *client) { struct gfio_client *gc = client->client_data; assert(gc->client == client); fio_put_client(gc->client); gc->client = NULL; } struct client_ops gfio_client_ops = { .text = gfio_text_op, .disk_util = gfio_disk_util_op, .thread_status = gfio_thread_status_op, .group_stats = gfio_group_stats_op, .jobs_eta = gfio_update_client_eta, .eta = gfio_update_all_eta, .probe = gfio_probe_op, .quit = gfio_quit_op, .add_job = gfio_add_job_op, .update_job = gfio_update_job_op, .timed_out = gfio_client_timed_out, .stop = gfio_client_stop, .start = gfio_client_start, .job_start = gfio_client_job_start, .iolog = gfio_client_iolog, .removed = gfio_client_removed, .eta_msec = FIO_CLIENT_DEF_ETA_MSEC, .stay_connected = 1, .client_type = FIO_CLIENT_TYPE_GUI, }; fio-2.1.3/gclient.h000066400000000000000000000006011222032232000140510ustar00rootroot00000000000000#ifndef GFIO_CLIENT_H #define GFIO_CLIENT_H extern struct client_ops gfio_client_ops; extern void gfio_display_end_results(struct gfio_client *); #define GFIO_READ_R 0.13 #define GFIO_READ_G 0.54 #define GFIO_READ_B 0.13 #define GFIO_WRITE_R 1.00 #define GFIO_WRITE_G 0.00 #define GFIO_WRITE_B 0.00 #define GFIO_TRIM_R 0.24 #define GFIO_TRIM_G 0.18 #define GFIO_TRIM_B 0.52 #endif fio-2.1.3/gcompat.c000066400000000000000000000023721222032232000140600ustar00rootroot00000000000000#include #include "gcompat.h" #if GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 24 GtkWidget *gtk_combo_box_text_new(void) { return gtk_combo_box_new(); } void gtk_combo_box_text_append_text(GtkComboBoxText *combo_box, const gchar *text) { gtk_combo_box_append_text(GTK_COMBO_BOX(combo_box), text); } void gtk_combo_box_text_insert_text(GtkComboBoxText *combo_box, gint position, const gchar *text) { gtk_combo_box_insert_text(GTK_COMBO_BOX(combo_box), position, text); } void gtk_combo_box_text_prepend_text(GtkComboBoxText *combo_box, const gchar *text) { gtk_combo_box_prepend_text(GTK_COMBO_BOX(combo_box), text); } gchar *gtk_combo_box_text_get_active_text(GtkComboBoxText *combo_box) { return gtk_combo_box_get_active_text(GTK_COMBO_BOX(combo_box)); } #endif #if GTK_MAJOR_VERSION < 3 guint gtk_widget_get_allocated_width(GtkWidget *w) { return w->allocation.width; } guint gtk_widget_get_allocated_height(GtkWidget *w) { return w->allocation.height; } #endif #if GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 18 void gtk_widget_set_can_focus(GtkWidget *widget, gboolean can_focus) { if (can_focus) GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_FOCUS); else GTK_WIDGET_UNSET_FLAGS(widget, GTK_CAN_FOCUS); } #endif fio-2.1.3/gcompat.h000066400000000000000000000026551222032232000140710ustar00rootroot00000000000000#ifndef GFIO_GTK_COMPAT #define GFIO_GTK_COMPAT #include #if GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 24 struct GtkComboBoxText; typedef GtkComboBox GtkComboBoxText; GtkWidget *gtk_combo_box_text_new(void); GtkWidget *gtk_combo_box_text_new_with_entry(void); void gtk_combo_box_text_append_text(GtkComboBoxText *combo_box, const gchar *text); void gtk_combo_box_text_insert_text(GtkComboBoxText *combo_box, gint position, const gchar *text); void gtk_combo_box_text_prepend_text(GtkComboBoxText *combo_box, const gchar *text); void gtk_combo_box_text_remove(GtkComboBoxText *combo_box, gint position); gchar *gtk_combo_box_text_get_active_text(GtkComboBoxText *combo_box); #define GTK_COMBO_BOX_TEXT GTK_COMBO_BOX #endif /* GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 24 */ #if GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 14 static inline GtkWidget *gtk_dialog_get_content_area(GtkDialog *dialog) { return dialog->vbox; } static inline GdkWindow *gtk_widget_get_window(GtkWidget *w) { return w->window; } #endif #if GTK_MAJOR_VERSION < 3 guint gtk_widget_get_allocated_width(GtkWidget *w); guint gtk_widget_get_allocated_height(GtkWidget *w); #endif #if GTK_MAJOR_VERSION == 3 #define GFIO_DRAW_EVENT "draw" #elif GTK_MAJOR_VERSION == 2 #define GFIO_DRAW_EVENT "expose_event" #endif #if GTK_MAJOR_VERSION <= 2 && GTK_MINOR_VERSION < 18 void gtk_widget_set_can_focus(GtkWidget *widget, gboolean can_focus); #endif #endif fio-2.1.3/gerror.c000066400000000000000000000041261222032232000137250ustar00rootroot00000000000000#include #include #include #include #include #include "gfio.h" #include "gerror.h" static void on_info_bar_response(GtkWidget *widget, gint response, gpointer data) { struct gui *ui = (struct gui *) data; if (response == GTK_RESPONSE_OK) { gtk_widget_destroy(widget); ui->error_info_bar = NULL; } } static void report_error(struct gui_entry *ge, GError *error) { struct gui *ui = ge->ui; if (ui->error_info_bar == NULL) { GtkWidget *container; ui->error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); g_signal_connect(ui->error_info_bar, "response", G_CALLBACK(on_info_bar_response), ui); gtk_info_bar_set_message_type(GTK_INFO_BAR(ui->error_info_bar), GTK_MESSAGE_ERROR); ui->error_label = gtk_label_new(error->message); container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui->error_info_bar)); gtk_container_add(GTK_CONTAINER(container), ui->error_label); gtk_box_pack_start(GTK_BOX(ui->vbox), ui->error_info_bar, FALSE, FALSE, 0); gtk_widget_show_all(ui->vbox); } else { char buffer[256]; snprintf(buffer, sizeof(buffer), "Failed to open file."); gtk_label_set_text(GTK_LABEL(ui->error_label), buffer); } } void gfio_report_error(struct gui_entry *ge, const char *format, ...) { va_list args; GError *error; va_start(args, format); error = g_error_new_valist(g_quark_from_string("fio"), 1, format, args); va_end(args); report_error(ge, error); g_error_free(error); } void gfio_report_info(struct gui *ui, const char *title, const char *message) { GtkWidget *dialog, *content, *label; dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(ui->window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); label = gtk_label_new(message); gtk_container_add(GTK_CONTAINER(content), label); gtk_widget_show_all(dialog); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } fio-2.1.3/gerror.h000066400000000000000000000003271222032232000137310ustar00rootroot00000000000000#ifndef GFIO_ERROR_H #define GFIO_ERROR_H extern void gfio_report_error(struct gui_entry *ge, const char *format, ...); extern void gfio_report_info(struct gui *ui, const char *title, const char *message); #endif fio-2.1.3/gettime-thread.c000066400000000000000000000030751222032232000153320ustar00rootroot00000000000000#include #include #include #include #include "fio.h" #include "smalloc.h" struct timeval *fio_tv = NULL; int fio_gtod_offload = 0; int fio_gtod_cpu = -1; static pthread_t gtod_thread; void fio_gtod_init(void) { fio_tv = smalloc(sizeof(struct timeval)); if (!fio_tv) log_err("fio: smalloc pool exhausted\n"); } static void fio_gtod_update(void) { if (fio_tv) gettimeofday(fio_tv, NULL); } static void *gtod_thread_main(void *data) { struct fio_mutex *mutex = data; fio_mutex_up(mutex); /* * As long as we have jobs around, update the clock. It would be nice * to have some way of NOT hammering that CPU with gettimeofday(), * but I'm not sure what to use outside of a simple CPU nop to relax * it - we don't want to lose precision. */ while (threads) { fio_gtod_update(); nop; } return NULL; } int fio_start_gtod_thread(void) { struct fio_mutex *mutex; pthread_attr_t attr; int ret; mutex = fio_mutex_init(FIO_MUTEX_LOCKED); if (!mutex) return 1; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); ret = pthread_create(>od_thread, &attr, gtod_thread_main, NULL); pthread_attr_destroy(&attr); if (ret) { log_err("Can't create gtod thread: %s\n", strerror(ret)); goto err; } ret = pthread_detach(gtod_thread); if (ret) { log_err("Can't detatch gtod thread: %s\n", strerror(ret)); goto err; } dprint(FD_MUTEX, "wait on startup_mutex\n"); fio_mutex_down(mutex); dprint(FD_MUTEX, "done waiting on startup_mutex\n"); err: fio_mutex_remove(mutex); return ret; } fio-2.1.3/gettime.c000066400000000000000000000300051222032232000140560ustar00rootroot00000000000000/* * Clock functions */ #include #include #include #include #include "fio.h" #include "smalloc.h" #include "hash.h" #include "os/os.h" #ifdef ARCH_HAVE_CPU_CLOCK static unsigned long cycles_per_usec; static unsigned long inv_cycles_per_usec; #endif int tsc_reliable = 0; struct tv_valid { struct timeval last_tv; uint64_t last_cycles; int last_tv_valid; }; #ifdef CONFIG_TLS_THREAD static __thread struct tv_valid static_tv_valid; #else static pthread_key_t tv_tls_key; #endif enum fio_cs fio_clock_source = FIO_PREFERRED_CLOCK_SOURCE; int fio_clock_source_set = 0; enum fio_cs fio_clock_source_inited = CS_INVAL; #ifdef FIO_DEBUG_TIME #define HASH_BITS 8 #define HASH_SIZE (1 << HASH_BITS) static struct flist_head hash[HASH_SIZE]; static int gtod_inited; struct gtod_log { struct flist_head list; void *caller; unsigned long calls; }; static struct gtod_log *find_hash(void *caller) { unsigned long h = hash_ptr(caller, HASH_BITS); struct flist_head *entry; flist_for_each(entry, &hash[h]) { struct gtod_log *log = flist_entry(entry, struct gtod_log, list); if (log->caller == caller) return log; } return NULL; } static struct gtod_log *find_log(void *caller) { struct gtod_log *log = find_hash(caller); if (!log) { unsigned long h; log = malloc(sizeof(*log)); INIT_FLIST_HEAD(&log->list); log->caller = caller; log->calls = 0; h = hash_ptr(caller, HASH_BITS); flist_add_tail(&log->list, &hash[h]); } return log; } static void gtod_log_caller(void *caller) { if (gtod_inited) { struct gtod_log *log = find_log(caller); log->calls++; } } static void fio_exit fio_dump_gtod(void) { unsigned long total_calls = 0; int i; for (i = 0; i < HASH_SIZE; i++) { struct flist_head *entry; struct gtod_log *log; flist_for_each(entry, &hash[i]) { log = flist_entry(entry, struct gtod_log, list); printf("function %p, calls %lu\n", log->caller, log->calls); total_calls += log->calls; } } printf("Total %lu gettimeofday\n", total_calls); } static void fio_init gtod_init(void) { int i; for (i = 0; i < HASH_SIZE; i++) INIT_FLIST_HEAD(&hash[i]); gtod_inited = 1; } #endif /* FIO_DEBUG_TIME */ #ifdef CONFIG_CLOCK_GETTIME static int fill_clock_gettime(struct timespec *ts) { #ifdef CONFIG_CLOCK_MONOTONIC return clock_gettime(CLOCK_MONOTONIC, ts); #else return clock_gettime(CLOCK_REALTIME, ts); #endif } #endif static void *__fio_gettime(struct timeval *tp) { struct tv_valid *tv; #ifdef CONFIG_TLS_THREAD tv = &static_tv_valid; #else tv = pthread_getspecific(tv_tls_key); #endif switch (fio_clock_source) { #ifdef CONFIG_GETTIMEOFDAY case CS_GTOD: gettimeofday(tp, NULL); break; #endif #ifdef CONFIG_CLOCK_GETTIME case CS_CGETTIME: { struct timespec ts; if (fill_clock_gettime(&ts) < 0) { log_err("fio: clock_gettime fails\n"); assert(0); } tp->tv_sec = ts.tv_sec; tp->tv_usec = ts.tv_nsec / 1000; break; } #endif #ifdef ARCH_HAVE_CPU_CLOCK case CS_CPUCLOCK: { uint64_t usecs, t; t = get_cpu_clock(); if (tv && t < tv->last_cycles) { dprint(FD_TIME, "CPU clock going back in time\n"); t = tv->last_cycles; } else if (tv) tv->last_cycles = t; usecs = (t * inv_cycles_per_usec) / 16777216UL; tp->tv_sec = usecs / 1000000; tp->tv_usec = usecs % 1000000; break; } #endif default: log_err("fio: invalid clock source %d\n", fio_clock_source); break; } return tv; } #ifdef FIO_DEBUG_TIME void fio_gettime(struct timeval *tp, void *caller) #else void fio_gettime(struct timeval *tp, void fio_unused *caller) #endif { struct tv_valid *tv; #ifdef FIO_DEBUG_TIME if (!caller) caller = __builtin_return_address(0); gtod_log_caller(caller); #endif if (fio_tv) { memcpy(tp, fio_tv, sizeof(*tp)); return; } tv = __fio_gettime(tp); /* * If Linux is using the tsc clock on non-synced processors, * sometimes time can appear to drift backwards. Fix that up. */ if (tv) { if (tv->last_tv_valid) { if (tp->tv_sec < tv->last_tv.tv_sec) tp->tv_sec = tv->last_tv.tv_sec; else if (tv->last_tv.tv_sec == tp->tv_sec && tp->tv_usec < tv->last_tv.tv_usec) tp->tv_usec = tv->last_tv.tv_usec; } tv->last_tv_valid = 1; memcpy(&tv->last_tv, tp, sizeof(*tp)); } } #ifdef ARCH_HAVE_CPU_CLOCK static unsigned long get_cycles_per_usec(void) { struct timeval s, e; uint64_t c_s, c_e; enum fio_cs old_cs = fio_clock_source; #ifdef CONFIG_CLOCK_GETTIME fio_clock_source = CS_CGETTIME; #else fio_clock_source = CS_GTOD; #endif __fio_gettime(&s); c_s = get_cpu_clock(); do { uint64_t elapsed; __fio_gettime(&e); elapsed = utime_since(&s, &e); if (elapsed >= 1280) { c_e = get_cpu_clock(); break; } } while (1); fio_clock_source = old_cs; return (c_e - c_s + 127) >> 7; } #define NR_TIME_ITERS 50 static int calibrate_cpu_clock(void) { double delta, mean, S; uint64_t avg, cycles[NR_TIME_ITERS]; int i, samples; cycles[0] = get_cycles_per_usec(); S = delta = mean = 0.0; for (i = 0; i < NR_TIME_ITERS; i++) { cycles[i] = get_cycles_per_usec(); delta = cycles[i] - mean; if (delta) { mean += delta / (i + 1.0); S += delta * (cycles[i] - mean); } } /* * The most common platform clock breakage is returning zero * indefinitely. Check for that and return failure. */ if (!cycles[0] && !cycles[NR_TIME_ITERS - 1]) return 1; S = sqrt(S / (NR_TIME_ITERS - 1.0)); samples = avg = 0; for (i = 0; i < NR_TIME_ITERS; i++) { double this = cycles[i]; if ((fmax(this, mean) - fmin(this, mean)) > S) continue; samples++; avg += this; } S /= (double) NR_TIME_ITERS; mean /= 10.0; for (i = 0; i < NR_TIME_ITERS; i++) dprint(FD_TIME, "cycles[%d]=%llu\n", i, (unsigned long long) cycles[i] / 10); avg /= samples; avg = (avg + 5) / 10; dprint(FD_TIME, "avg: %llu\n", (unsigned long long) avg); dprint(FD_TIME, "mean=%f, S=%f\n", mean, S); cycles_per_usec = avg; inv_cycles_per_usec = 16777216UL / cycles_per_usec; dprint(FD_TIME, "inv_cycles_per_usec=%lu\n", inv_cycles_per_usec); return 0; } #else static int calibrate_cpu_clock(void) { return 1; } #endif #ifndef CONFIG_TLS_THREAD void fio_local_clock_init(int is_thread) { struct tv_valid *t; t = calloc(sizeof(*t), 1); if (pthread_setspecific(tv_tls_key, t)) log_err("fio: can't set TLS key\n"); } static void kill_tv_tls_key(void *data) { free(data); } #else void fio_local_clock_init(int is_thread) { } #endif void fio_clock_init(void) { if (fio_clock_source == fio_clock_source_inited) return; #ifndef CONFIG_TLS_THREAD if (pthread_key_create(&tv_tls_key, kill_tv_tls_key)) log_err("fio: can't create TLS key\n"); #endif fio_clock_source_inited = fio_clock_source; if (calibrate_cpu_clock()) tsc_reliable = 0; /* * If the arch sets tsc_reliable != 0, then it must be good enough * to use as THE clock source. For x86 CPUs, this means the TSC * runs at a constant rate and is synced across CPU cores. */ if (tsc_reliable) { if (!fio_clock_source_set) fio_clock_source = CS_CPUCLOCK; } else if (fio_clock_source == CS_CPUCLOCK) log_info("fio: clocksource=cpu may not be reliable\n"); } uint64_t utime_since(struct timeval *s, struct timeval *e) { long sec, usec; uint64_t ret; sec = e->tv_sec - s->tv_sec; usec = e->tv_usec - s->tv_usec; if (sec > 0 && usec < 0) { sec--; usec += 1000000; } /* * time warp bug on some kernels? */ if (sec < 0 || (sec == 0 && usec < 0)) return 0; ret = sec * 1000000ULL + usec; return ret; } uint64_t utime_since_now(struct timeval *s) { struct timeval t; fio_gettime(&t, NULL); return utime_since(s, &t); } uint64_t mtime_since(struct timeval *s, struct timeval *e) { long sec, usec, ret; sec = e->tv_sec - s->tv_sec; usec = e->tv_usec - s->tv_usec; if (sec > 0 && usec < 0) { sec--; usec += 1000000; } if (sec < 0 || (sec == 0 && usec < 0)) return 0; sec *= 1000UL; usec /= 1000UL; ret = sec + usec; return ret; } uint64_t mtime_since_now(struct timeval *s) { struct timeval t; void *p = __builtin_return_address(0); fio_gettime(&t, p); return mtime_since(s, &t); } uint64_t time_since_now(struct timeval *s) { return mtime_since_now(s) / 1000; } #if defined(FIO_HAVE_CPU_AFFINITY) && defined(ARCH_HAVE_CPU_CLOCK) && \ defined(CONFIG_SFAA) #define CLOCK_ENTRIES 100000 struct clock_entry { uint32_t seq; uint32_t cpu; uint64_t tsc; }; struct clock_thread { pthread_t thread; int cpu; pthread_mutex_t lock; pthread_mutex_t started; uint32_t *seq; struct clock_entry *entries; }; static inline uint32_t atomic32_inc_return(uint32_t *seq) { return 1 + __sync_fetch_and_add(seq, 1); } static void *clock_thread_fn(void *data) { struct clock_thread *t = data; struct clock_entry *c; os_cpu_mask_t cpu_mask; uint32_t last_seq; int i; memset(&cpu_mask, 0, sizeof(cpu_mask)); fio_cpu_set(&cpu_mask, t->cpu); if (fio_setaffinity(gettid(), cpu_mask) == -1) { log_err("clock setaffinity failed\n"); return (void *) 1; } pthread_mutex_lock(&t->lock); pthread_mutex_unlock(&t->started); last_seq = 0; c = &t->entries[0]; for (i = 0; i < CLOCK_ENTRIES; i++, c++) { uint32_t seq; uint64_t tsc; c->cpu = t->cpu; do { seq = atomic32_inc_return(t->seq); if (seq < last_seq) break; tsc = get_cpu_clock(); } while (seq != *t->seq); c->seq = seq; c->tsc = tsc; } log_info("cs: cpu%3d: %llu clocks seen\n", t->cpu, (unsigned long long) t->entries[i - 1].tsc - t->entries[0].tsc); /* * The most common platform clock breakage is returning zero * indefinitely. Check for that and return failure. */ if (!t->entries[i - 1].tsc && !t->entries[0].tsc) return (void *) 1; return NULL; } static int clock_cmp(const void *p1, const void *p2) { const struct clock_entry *c1 = p1; const struct clock_entry *c2 = p2; if (c1->seq == c2->seq) log_err("cs: bug in atomic sequence!\n"); return c1->seq - c2->seq; } int fio_monotonic_clocktest(void) { struct clock_thread *threads; unsigned int nr_cpus = cpus_online(); struct clock_entry *entries; unsigned long tentries, failed; struct clock_entry *prev, *this; uint32_t seq = 0; int i; log_info("cs: reliable_tsc: %s\n", tsc_reliable ? "yes" : "no"); fio_debug |= 1U << FD_TIME; calibrate_cpu_clock(); fio_debug &= ~(1U << FD_TIME); threads = malloc(nr_cpus * sizeof(struct clock_thread)); tentries = CLOCK_ENTRIES * nr_cpus; entries = malloc(tentries * sizeof(struct clock_entry)); log_info("cs: Testing %u CPUs\n", nr_cpus); for (i = 0; i < nr_cpus; i++) { struct clock_thread *t = &threads[i]; t->cpu = i; t->seq = &seq; t->entries = &entries[i * CLOCK_ENTRIES]; pthread_mutex_init(&t->lock, NULL); pthread_mutex_init(&t->started, NULL); pthread_mutex_lock(&t->lock); pthread_create(&t->thread, NULL, clock_thread_fn, t); } for (i = 0; i < nr_cpus; i++) { struct clock_thread *t = &threads[i]; pthread_mutex_lock(&t->started); } for (i = 0; i < nr_cpus; i++) { struct clock_thread *t = &threads[i]; pthread_mutex_unlock(&t->lock); } for (failed = i = 0; i < nr_cpus; i++) { struct clock_thread *t = &threads[i]; void *ret; pthread_join(t->thread, &ret); if (ret) failed++; } free(threads); if (failed) { log_err("Clocksource test: %lu threads failed\n", failed); goto err; } qsort(entries, tentries, sizeof(struct clock_entry), clock_cmp); for (failed = i = 0; i < tentries; i++) { this = &entries[i]; if (!i) { prev = this; continue; } if (prev->tsc > this->tsc) { uint64_t diff = prev->tsc - this->tsc; log_info("cs: CPU clock mismatch (diff=%llu):\n", (unsigned long long) diff); log_info("\t CPU%3u: TSC=%llu, SEQ=%u\n", prev->cpu, (unsigned long long) prev->tsc, prev->seq); log_info("\t CPU%3u: TSC=%llu, SEQ=%u\n", this->cpu, (unsigned long long) this->tsc, this->seq); failed++; } prev = this; } if (failed) log_info("cs: Failed: %lu\n", failed); else log_info("cs: Pass!\n"); err: free(entries); return !!failed; } #else /* defined(FIO_HAVE_CPU_AFFINITY) && defined(ARCH_HAVE_CPU_CLOCK) */ int fio_monotonic_clocktest(void) { log_info("cs: current platform does not support CPU clocks\n"); return 0; } #endif fio-2.1.3/gettime.h000066400000000000000000000006441222032232000140710ustar00rootroot00000000000000#ifndef FIO_GETTIME_H #define FIO_GETTIME_H /* * Clock sources */ enum fio_cs { CS_GTOD = 1, CS_CGETTIME, CS_CPUCLOCK, CS_INVAL, }; extern void fio_gettime(struct timeval *, void *); extern void fio_gtod_init(void); extern void fio_clock_init(void); extern int fio_start_gtod_thread(void); extern int fio_monotonic_clocktest(void); extern void fio_local_clock_init(int); extern struct timeval *fio_tv; #endif fio-2.1.3/gfio.c000066400000000000000000001465331222032232000133620ustar00rootroot00000000000000/* * gfio - gui front end for fio - the flexible io tester * * Copyright (C) 2012 Stephen M. Cameron * Copyright (C) 2012 Jens Axboe * * The license below covers all files distributed with fio unless otherwise * noted in the file itself. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include "fio.h" #include "gfio.h" #include "ghelpers.h" #include "goptions.h" #include "gerror.h" #include "gclient.h" #include "graph.h" static int gfio_server_running; static unsigned int gfio_graph_limit = 100; GdkColor gfio_color_white; GdkColor gfio_color_lightyellow; const char *gfio_graph_font = GRAPH_DEFAULT_FONT; typedef void (*clickfunction)(GtkWidget *widget, gpointer data); static void connect_clicked(GtkWidget *widget, gpointer data); static void start_job_clicked(GtkWidget *widget, gpointer data); static void send_clicked(GtkWidget *widget, gpointer data); static struct button_spec { const char *buttontext; clickfunction f; const char *tooltiptext[2]; const int start_sensitive; } buttonspeclist[] = { { .buttontext = "Connect", .f = connect_clicked, .tooltiptext = { "Disconnect from host", "Connect to host" }, .start_sensitive = 1, }, { .buttontext = "Send", .f = send_clicked, .tooltiptext = { "Send job description to host", NULL }, .start_sensitive = 0, }, { .buttontext = "Start Job", .f = start_job_clicked, .tooltiptext = { "Start the current job on the server", NULL }, .start_sensitive = 0, }, }; static void setup_iops_graph(struct gfio_graphs *gg) { struct graph *g; g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font); graph_title(g, "IOPS (IOs/sec)"); graph_x_title(g, "Time (secs)"); gg->read_iops = graph_add_label(g, "Read IOPS"); gg->write_iops = graph_add_label(g, "Write IOPS"); gg->trim_iops = graph_add_label(g, "Trim IOPS"); graph_set_color(g, gg->read_iops, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B); graph_set_color(g, gg->write_iops, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B); graph_set_color(g, gg->trim_iops, GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B); line_graph_set_data_count_limit(g, gfio_graph_limit); graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0); graph_set_graph_all_zeroes(g, 0); gg->iops_graph = g; } static void setup_bandwidth_graph(struct gfio_graphs *gg) { struct graph *g; g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font); graph_title(g, "Bandwidth (bytes/sec)"); graph_x_title(g, "Time (secs)"); gg->read_bw = graph_add_label(g, "Read Bandwidth"); gg->write_bw = graph_add_label(g, "Write Bandwidth"); gg->trim_bw = graph_add_label(g, "Trim Bandwidth"); graph_set_color(g, gg->read_bw, GFIO_READ_R, GFIO_READ_G, GFIO_READ_B); graph_set_color(g, gg->write_bw, GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B); graph_set_color(g, gg->trim_bw, GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B); graph_set_base_offset(g, 1); line_graph_set_data_count_limit(g, 100); graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0); graph_set_graph_all_zeroes(g, 0); gg->bandwidth_graph = g; } static void setup_graphs(struct gfio_graphs *g) { setup_iops_graph(g); setup_bandwidth_graph(g); } void clear_ge_ui_info(struct gui_entry *ge) { gtk_label_set_text(GTK_LABEL(ge->probe.hostname), ""); gtk_label_set_text(GTK_LABEL(ge->probe.os), ""); gtk_label_set_text(GTK_LABEL(ge->probe.arch), ""); gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), ""); #if 0 /* should we empty it... */ gtk_entry_set_text(GTK_ENTRY(ge->eta.name), ""); #endif multitext_update_entry(&ge->eta.iotype, 0, ""); multitext_update_entry(&ge->eta.bs, 0, ""); multitext_update_entry(&ge->eta.ioengine, 0, ""); multitext_update_entry(&ge->eta.iodepth, 0, ""); gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), ""); gtk_entry_set_text(GTK_ENTRY(ge->eta.files), ""); gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), ""); gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), ""); gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), ""); gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), ""); } static void set_menu_entry_text(struct gui *ui, const char *path, const char *text) { GtkWidget *w; w = gtk_ui_manager_get_widget(ui->uimanager, path); if (w) gtk_menu_item_set_label(GTK_MENU_ITEM(w), text); else fprintf(stderr, "gfio: can't find path %s\n", path); } static void set_menu_entry_visible(struct gui *ui, const char *path, int show) { GtkWidget *w; w = gtk_ui_manager_get_widget(ui->uimanager, path); if (w) gtk_widget_set_sensitive(w, show); else fprintf(stderr, "gfio: can't find path %s\n", path); } static void set_job_menu_visible(struct gui *ui, int visible) { set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible); } static void set_view_results_visible(struct gui *ui, int visible) { set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible); } static const char *get_button_tooltip(struct button_spec *s, int sensitive) { if (s->tooltiptext[sensitive]) return s->tooltiptext[sensitive]; return s->tooltiptext[0]; } static GtkWidget *add_button(GtkWidget *buttonbox, struct button_spec *buttonspec, gpointer data) { GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext); gboolean sens = buttonspec->start_sensitive; g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data); gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3); sens = buttonspec->start_sensitive; gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens)); gtk_widget_set_sensitive(button, sens); return button; } static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist, int nbuttons) { int i; for (i = 0; i < nbuttons; i++) ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge); } /* * Update sensitivity of job buttons and job menu items, based on the * state of the client. */ static void update_button_states(struct gui *ui, struct gui_entry *ge) { unsigned int connect_state, send_state, start_state, edit_state; const char *connect_str = NULL; switch (ge->state) { default: gfio_report_error(ge, "Bad client state: %u\n", ge->state); /* fall through to new state */ case GE_STATE_NEW: connect_state = 1; edit_state = 1; connect_str = "Connect"; send_state = 0; start_state = 0; break; case GE_STATE_CONNECTED: connect_state = 1; edit_state = 1; connect_str = "Disconnect"; send_state = 1; start_state = 0; break; case GE_STATE_JOB_SENT: connect_state = 1; edit_state = 1; connect_str = "Disconnect"; send_state = 0; start_state = 1; break; case GE_STATE_JOB_STARTED: connect_state = 1; edit_state = 1; connect_str = "Disconnect"; send_state = 0; start_state = 1; break; case GE_STATE_JOB_RUNNING: connect_state = 1; edit_state = 0; connect_str = "Disconnect"; send_state = 0; start_state = 0; break; case GE_STATE_JOB_DONE: connect_state = 1; edit_state = 0; connect_str = "Connect"; send_state = 0; start_state = 0; break; } gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state); gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state); gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state); gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str); gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state)); set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state); set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str); set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state); set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state); set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state); if (ge->client && ge->client->nr_results) set_view_results_visible(ui, 1); else set_view_results_visible(ui, 0); } void gfio_set_state(struct gui_entry *ge, unsigned int state) { ge->state = state; update_button_states(ge->ui, ge); } static void gfio_ui_setup_log(struct gui *ui) { GtkTreeSelection *selection; GtkListStore *model; GtkWidget *tree_view; model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); gtk_widget_set_can_focus(tree_view, FALSE); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE); g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE, "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL); tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE); tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE); tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE); tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE); ui->log_model = model; ui->log_tree = tree_view; } static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event, gpointer data) { guint width = gtk_widget_get_allocated_width(w); guint height = gtk_widget_get_allocated_height(w); struct gfio_graphs *g = data; graph_set_size(g->iops_graph, width / 2.0, height); graph_set_position(g->iops_graph, width / 2.0, 0.0); graph_set_size(g->bandwidth_graph, width / 2.0, height); graph_set_position(g->bandwidth_graph, 0, 0); return TRUE; } static void draw_graph(struct graph *g, cairo_t *cr) { line_graph_draw(g, cr); cairo_stroke(cr); } static gboolean graph_tooltip(GtkWidget *w, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer data) { struct gfio_graphs *g = data; const char *text = NULL; if (graph_contains_xy(g->iops_graph, x, y)) text = graph_find_tooltip(g->iops_graph, x, y); else if (graph_contains_xy(g->bandwidth_graph, x, y)) text = graph_find_tooltip(g->bandwidth_graph, x, y); if (text) { gtk_tooltip_set_text(tooltip, text); return TRUE; } return FALSE; } static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p) { struct gfio_graphs *g = p; cairo_t *cr; cr = gdk_cairo_create(gtk_widget_get_window(w)); if (graph_has_tooltips(g->iops_graph) || graph_has_tooltips(g->bandwidth_graph)) { g_object_set(w, "has-tooltip", TRUE, NULL); g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g); } cairo_set_source_rgb(cr, 0, 0, 0); draw_graph(g->iops_graph, cr); draw_graph(g->bandwidth_graph, cr); cairo_destroy(cr); return FALSE; } /* * FIXME: need more handling here */ static void ge_destroy(struct gui_entry *ge) { struct gfio_client *gc = ge->client; if (gc) { if (gc->client) { if (ge->state >= GE_STATE_CONNECTED) fio_client_terminate(gc->client); fio_put_client(gc->client); } free(gc); } g_hash_table_remove(ge->ui->ge_hash, &ge->page_num); free(ge->job_file); free(ge->host); free(ge); } static void ge_widget_destroy(GtkWidget *w, gpointer data) { struct gui_entry *ge = (struct gui_entry *) data; ge_destroy(ge); } static void gfio_quit(struct gui *ui) { gtk_main_quit(); } static void quit_clicked(__attribute__((unused)) GtkWidget *widget, gpointer data) { struct gui *ui = (struct gui *) data; gfio_quit(ui); } static void *job_thread(void *arg) { struct gui *ui = arg; ui->handler_running = 1; fio_handle_clients(&gfio_client_ops); ui->handler_running = 0; return NULL; } static int send_job_file(struct gui_entry *ge) { struct gfio_client *gc = ge->client; int ret = 0; /* * Prune old options, we are expecting the return options * when the job file is parsed remotely and returned to us. */ while (!flist_empty(&gc->o_list)) { struct gfio_client_options *gco; gco = flist_entry(gc->o_list.next, struct gfio_client_options, list); flist_del(&gco->list); free(gco); } ret = fio_client_send_ini(gc->client, ge->job_file); if (!ret) return 0; gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret)); return 1; } static void *server_thread(void *arg) { is_backend = 1; gfio_server_running = 1; fio_start_server(NULL); gfio_server_running = 0; return NULL; } static void gfio_start_server(struct gui *ui) { if (!gfio_server_running) { gfio_server_running = 1; pthread_create(&ui->server_t, NULL, server_thread, NULL); pthread_detach(ui->server_t); } } static void start_job_clicked(__attribute__((unused)) GtkWidget *widget, gpointer data) { struct gui_entry *ge = data; struct gfio_client *gc = ge->client; if (gc) fio_start_client(gc->client); } static void file_open(GtkWidget *w, gpointer data); struct connection_widgets { GtkWidget *hentry; GtkWidget *combo; GtkWidget *button; }; static void hostname_cb(GtkEntry *entry, gpointer data) { struct connection_widgets *cw = data; int uses_net = 0, is_localhost = 0; const gchar *text; gchar *ctext; /* * Check whether to display the 'auto start backend' box * or not. Show it if we are a localhost and using network, * or using a socket. */ ctext = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw->combo)); if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4)) uses_net = 1; g_free(ctext); if (uses_net) { text = gtk_entry_get_text(GTK_ENTRY(cw->hentry)); if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") || !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") || !strcmp(text, "ip6-loopback")) is_localhost = 1; } if (!uses_net || is_localhost) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1); gtk_widget_set_sensitive(cw->button, 1); } else { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0); gtk_widget_set_sensitive(cw->button, 0); } } static int get_connection_details(struct gui_entry *ge) { GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry; struct connection_widgets cw; struct gui *ui = ge->ui; char *typeentry; if (ge->host) return 0; dialog = gtk_dialog_new_with_buttons("Connection details", GTK_WINDOW(ui->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); frame = gtk_frame_new("Hostname / socket name"); vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); box = gtk_vbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(frame), box); hbox = gtk_hbox_new(TRUE, 10); gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0); cw.hentry = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost"); gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0); frame = gtk_frame_new("Port"); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); box = gtk_vbox_new(FALSE, 10); gtk_container_add(GTK_CONTAINER(frame), box); hbox = gtk_hbox_new(TRUE, 4); gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0); pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT); frame = gtk_frame_new("Type"); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); box = gtk_vbox_new(FALSE, 10); gtk_container_add(GTK_CONTAINER(frame), box); hbox = gtk_hbox_new(TRUE, 4); gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0); cw.combo = gtk_combo_box_text_new(); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv4"); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv6"); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "local socket"); gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0); gtk_container_add(GTK_CONTAINER(hbox), cw.combo); frame = gtk_frame_new("Options"); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); box = gtk_vbox_new(FALSE, 10); gtk_container_add(GTK_CONTAINER(frame), box); hbox = gtk_hbox_new(TRUE, 4); gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0); cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1); gtk_widget_set_tooltip_text(cw.button, "When running fio locally, it is necessary to have the backend running on the same system. If this is checked, gfio will start the backend automatically for you if it isn't already running."); gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6); /* * Connect edit signal, so we can show/not-show the auto start button */ g_signal_connect(G_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw); g_signal_connect(G_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw); gtk_widget_show_all(dialog); if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) { gtk_widget_destroy(dialog); return 1; } ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry))); ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry)); typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw.combo)); if (!typeentry || !strncmp(typeentry, "IPv4", 4)) ge->type = Fio_client_ipv4; else if (!strncmp(typeentry, "IPv6", 4)) ge->type = Fio_client_ipv6; else ge->type = Fio_client_socket; g_free(typeentry); ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button)); gtk_widget_destroy(dialog); return 0; } static void gfio_set_client(struct gfio_client *gc, struct fio_client *client) { gc->client = fio_get_client(client); client->client_data = gc; } static void gfio_client_added(struct gui_entry *ge, struct fio_client *client) { struct gfio_client_options *gco; struct gfio_client *gc; gc = calloc(1, sizeof(*gc)); INIT_FLIST_HEAD(&gc->o_list); gc->ge = ge; ge->client = gc; gfio_set_client(gc, client); /* * Just add a default set of options, need to consider how best * to handle this */ gco = calloc(1, sizeof(*gco)); INIT_FLIST_HEAD(&gco->list); options_default_fill(&gco->o); flist_add_tail(&gco->list, &gc->o_list); gc->o_list_nr++; } static void gfio_clear_graph_data(struct gfio_graphs *g) { graph_clear_values(g->iops_graph); graph_clear_values(g->bandwidth_graph); } static void connect_clicked(GtkWidget *widget, gpointer data) { struct gui_entry *ge = data; struct gfio_client *gc = ge->client; if (ge->state == GE_STATE_NEW) { int ret; if (!ge->job_file) file_open(widget, ge->ui); if (!ge->job_file) return; gc = ge->client; if (!gc->client) { struct fio_client *client; if (get_connection_details(ge)) { gfio_report_error(ge, "Failed to get connection details\n"); return; } client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port); if (!client) { gfio_report_error(ge, "Failed to add client %s\n", ge->host); free(ge->host); ge->host = NULL; return; } gfio_set_client(gc, client); } gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running"); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0); ret = fio_client_connect(gc->client); if (!ret) { if (!ge->ui->handler_running) pthread_create(&ge->ui->t, NULL, job_thread, ge->ui); gfio_set_state(ge, GE_STATE_CONNECTED); gfio_clear_graph_data(&ge->graphs); } else { gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret)); } } else { fio_client_terminate(gc->client); gfio_set_state(ge, GE_STATE_NEW); clear_ge_ui_info(ge); } } static void send_clicked(GtkWidget *widget, gpointer data) { struct gui_entry *ge = data; if (send_job_file(ge)) gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1); } static GtkWidget *new_client_page(struct gui_entry *ge); static struct gui_entry *alloc_new_gui_entry(struct gui *ui) { struct gui_entry *ge; ge = malloc(sizeof(*ge)); memset(ge, 0, sizeof(*ge)); ge->state = GE_STATE_NEW; ge->ui = ui; return ge; } static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name) { struct gui_entry *ge; ge = alloc_new_gui_entry(ui); ge->vbox = new_client_page(ge); g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge); ge->page_label = gtk_label_new(name); ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label); g_hash_table_insert(ui->ge_hash, &ge->page_num, ge); gtk_widget_show_all(ui->window); return ge; } static void file_new(GtkWidget *w, gpointer data) { struct gui *ui = (struct gui *) data; struct gui_entry *ge; ge = get_new_ge_with_tab(ui, "Untitled"); gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num); } /* * Return the 'ge' corresponding to the tab. If the active tab is the * main tab, open a new tab. */ static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page, int *created) { if (!cur_page) { if (created) *created = 1; return get_new_ge_with_tab(ui, "Untitled"); } if (created) *created = 0; return g_hash_table_lookup(ui->ge_hash, &cur_page); } static struct gui_entry *get_ge_from_cur_tab(struct gui *ui) { gint cur_page; /* * Main tab is tab 0, so any current page other than 0 holds * a ge entry. */ cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook)); if (cur_page) return get_ge_from_page(ui, cur_page, NULL); return NULL; } static void file_close(GtkWidget *w, gpointer data) { struct gui *ui = (struct gui *) data; struct gui_entry *ge; /* * Can't close the main tab */ ge = get_ge_from_cur_tab(ui); if (ge) { gtk_widget_destroy(ge->vbox); return; } if (g_hash_table_size(ui->ge_hash)) { gfio_report_info(ui, "Error", "The main page view cannot be closed\n"); return; } gfio_quit(ui); } static void file_add_recent(struct gui *ui, const gchar *uri) { GtkRecentData grd; memset(&grd, 0, sizeof(grd)); grd.display_name = strdup("gfio"); grd.description = strdup("Fio job file"); grd.mime_type = strdup(GFIO_MIME); grd.app_name = strdup(g_get_application_name()); grd.app_exec = strdup("gfio %f/%u"); gtk_recent_manager_add_full(ui->recentmanager, uri, &grd); } static gchar *get_filename_from_uri(const gchar *uri) { if (strncmp(uri, "file://", 7)) return strdup(uri); return strdup(uri + 7); } static int do_file_open(struct gui_entry *ge, const gchar *uri) { struct fio_client *client; assert(!ge->job_file); ge->job_file = get_filename_from_uri(uri); client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port); if (client) { char *label = strdup(uri); basename(label); gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label)); free(label); gfio_client_added(ge, client); file_add_recent(ge->ui, uri); return 0; } gfio_report_error(ge, "Failed to add client %s\n", ge->host); free(ge->host); ge->host = NULL; free(ge->job_file); ge->job_file = NULL; return 1; } static int do_file_open_with_tab(struct gui *ui, const gchar *uri) { struct gui_entry *ge; gint cur_page; int ret, ge_is_new = 0; /* * Creates new tab if current tab is the main window, or the * current tab already has a client. */ cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook)); ge = get_ge_from_page(ui, cur_page, &ge_is_new); if (ge->client) { ge = get_new_ge_with_tab(ui, "Untitled"); ge_is_new = 1; } gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num); if (get_connection_details(ge)) { if (ge_is_new) gtk_widget_destroy(ge->vbox); return 1; } ret = do_file_open(ge, uri); if (!ret) { if (ge->server_start) gfio_start_server(ui); } else { if (ge_is_new) gtk_widget_destroy(ge->vbox); } return ret; } static void recent_open(GtkAction *action, gpointer data) { struct gui *ui = (struct gui *) data; GtkRecentInfo *info; const gchar *uri; info = g_object_get_data(G_OBJECT(action), "gtk-recent-info"); uri = gtk_recent_info_get_uri(info); do_file_open_with_tab(ui, uri); } static void file_open(GtkWidget *w, gpointer data) { struct gui *ui = data; GtkWidget *dialog; GtkFileFilter *filter; gchar *filename; dialog = gtk_file_chooser_dialog_new("Open File", GTK_WINDOW(ui->window), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); filter = gtk_file_filter_new(); gtk_file_filter_add_pattern(filter, "*.fio"); gtk_file_filter_add_pattern(filter, "*.job"); gtk_file_filter_add_pattern(filter, "*.ini"); gtk_file_filter_add_mime_type(filter, GFIO_MIME); gtk_file_filter_set_name(filter, "Fio job file"); gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter); if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) { gtk_widget_destroy(dialog); return; } filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); gtk_widget_destroy(dialog); do_file_open_with_tab(ui, filename); g_free(filename); } static void file_save(GtkWidget *w, gpointer data) { struct gui *ui = data; GtkWidget *dialog; dialog = gtk_file_chooser_dialog_new("Save File", GTK_WINDOW(ui->window), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document"); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { char *filename; filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); // save_job_file(filename); g_free(filename); } gtk_widget_destroy(dialog); } static void view_log_destroy(GtkWidget *w, gpointer data) { struct gui *ui = (struct gui *) data; g_object_ref(G_OBJECT(ui->log_tree)); gtk_container_remove(GTK_CONTAINER(w), ui->log_tree); gtk_widget_destroy(w); ui->log_view = NULL; } void gfio_view_log(struct gui *ui) { GtkWidget *win, *scroll, *vbox, *box; if (ui->log_view) return; ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(win), "Log"); gtk_window_set_default_size(GTK_WINDOW(win), 700, 500); scroll = gtk_scrolled_window_new(NULL, NULL); gtk_container_set_border_width(GTK_CONTAINER(scroll), 5); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); box = gtk_hbox_new(TRUE, 0); gtk_box_pack_start(GTK_BOX(box), ui->log_tree, TRUE, TRUE, 0); g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box); vbox = gtk_vbox_new(TRUE, 5); gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0); gtk_container_add(GTK_CONTAINER(win), vbox); gtk_widget_show_all(win); } static void view_log(GtkWidget *w, gpointer data) { struct gui *ui = (struct gui *) data; gfio_view_log(ui); } static void connect_job_entry(GtkWidget *w, gpointer data) { struct gui *ui = (struct gui *) data; struct gui_entry *ge; ge = get_ge_from_cur_tab(ui); if (ge) connect_clicked(w, ge); } static void send_job_entry(GtkWidget *w, gpointer data) { struct gui *ui = (struct gui *) data; struct gui_entry *ge; ge = get_ge_from_cur_tab(ui); if (ge) send_clicked(w, ge); } static void edit_job_entry(GtkWidget *w, gpointer data) { struct gui *ui = (struct gui *) data; struct gui_entry *ge; ge = get_ge_from_cur_tab(ui); if (ge && ge->client) gopt_get_options_window(ui->window, ge->client); } static void start_job_entry(GtkWidget *w, gpointer data) { struct gui *ui = (struct gui *) data; struct gui_entry *ge; ge = get_ge_from_cur_tab(ui); if (ge) start_job_clicked(w, ge); } static void view_results(GtkWidget *w, gpointer data) { struct gui *ui = (struct gui *) data; struct gfio_client *gc; struct gui_entry *ge; ge = get_ge_from_cur_tab(ui); if (!ge) return; if (ge->results_window) return; gc = ge->client; if (gc && gc->nr_results) gfio_display_end_results(gc); } static void __update_graph_settings(struct gfio_graphs *g) { line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit); graph_set_font(g->iops_graph, gfio_graph_font); line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit); graph_set_font(g->bandwidth_graph, gfio_graph_font); } static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data) { struct gui_entry *ge = (struct gui_entry *) value; GdkEvent *ev; __update_graph_settings(&ge->graphs); ev = gdk_event_new(GDK_EXPOSE); g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs); gdk_event_free(ev); } static void update_graph_limits(void) { struct gui *ui = &main_ui; GdkEvent *ev; __update_graph_settings(&ui->graphs); ev = gdk_event_new(GDK_EXPOSE); g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT, GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs); gdk_event_free(ev); g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL); } static void preferences(GtkWidget *w, gpointer data) { GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font; GtkWidget *hbox, *spin, *entry, *spin_int; struct gui *ui = (struct gui *) data; int i; dialog = gtk_dialog_new_with_buttons("Preferences", GTK_WINDOW(ui->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); frame = gtk_frame_new("Graphing"); vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); vbox = gtk_vbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(frame), vbox); hbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); entry = gtk_label_new("Font face to use for graph labels"); gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5); font = gtk_font_button_new_with_font(gfio_graph_font); gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5); box = gtk_vbox_new(FALSE, 6); gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5); hbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5); entry = gtk_label_new("Maximum number of data points in graph (seconds)"); gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5); spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit); box = gtk_vbox_new(FALSE, 6); gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5); hbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5); entry = gtk_label_new("Client ETA request interval (msec)"); gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5); spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec); frame = gtk_frame_new("Debug logging"); vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); vbox = gtk_vbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(frame), vbox); box = gtk_hbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(vbox), box); buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX); for (i = 0; i < FD_DEBUG_MAX; i++) { if (i == 7) { box = gtk_hbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(vbox), box); } buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name); gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help); gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6); } gtk_widget_show_all(dialog); if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) { gtk_widget_destroy(dialog); return; } for (i = 0; i < FD_DEBUG_MAX; i++) { int set; set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i])); if (set) fio_debug |= (1UL << i); } gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font))); gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin)); update_graph_limits(); gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int)); gtk_widget_destroy(dialog); } static void about_dialog(GtkWidget *w, gpointer data) { const char *authors[] = { "Jens Axboe ", "Stephen Carmeron ", NULL }; const char *license[] = { "Fio is free software; you can redistribute it and/or modify " "it under the terms of the GNU General Public License as published by " "the Free Software Foundation; either version 2 of the License, or " "(at your option) any later version.\n", "Fio is distributed in the hope that it will be useful, " "but WITHOUT ANY WARRANTY; without even the implied warranty of " "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the " "GNU General Public License for more details.\n", "You should have received a copy of the GNU General Public License " "along with Fio; if not, write to the Free Software Foundation, Inc., " "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n" }; char *license_trans; license_trans = g_strconcat(license[0], "\n", license[1], "\n", license[2], "\n", NULL); gtk_show_about_dialog(NULL, "program-name", "gfio", "comments", "Gtk2 UI for fio", "license", license_trans, "website", "http://git.kernel.dk/?p=fio.git;a=summary", "authors", authors, "version", fio_version_string, "copyright", "© 2012 Jens Axboe ", "logo-icon-name", "fio", /* Must be last: */ "wrap-license", TRUE, NULL); g_free(license_trans); } static GtkActionEntry menu_items[] = { { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL}, { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL}, { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL}, { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL}, { "NewFile", GTK_STOCK_NEW, "New", "N", NULL, G_CALLBACK(file_new) }, { "CloseFile", GTK_STOCK_CLOSE, "Close", "W", NULL, G_CALLBACK(file_close) }, { "OpenFile", GTK_STOCK_OPEN, NULL, "O", NULL, G_CALLBACK(file_open) }, { "SaveFile", GTK_STOCK_SAVE, NULL, "S", NULL, G_CALLBACK(file_save) }, { "Preferences", GTK_STOCK_PREFERENCES, NULL, "p", NULL, G_CALLBACK(preferences) }, { "ViewLog", NULL, "Log", "l", NULL, G_CALLBACK(view_log) }, { "ViewResults", NULL, "Results", "R", NULL, G_CALLBACK(view_results) }, { "ConnectJob", NULL, "Connect", "D", NULL, G_CALLBACK(connect_job_entry) }, { "EditJob", NULL, "Edit job", "E", NULL, G_CALLBACK(edit_job_entry) }, { "SendJob", NULL, "Send job", "X", NULL, G_CALLBACK(send_job_entry) }, { "StartJob", NULL, "Start job", "L", NULL, G_CALLBACK(start_job_entry) }, { "Quit", GTK_STOCK_QUIT, NULL, "Q", NULL, G_CALLBACK(quit_clicked) }, { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) }, }; static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]); static const gchar *ui_string = " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "; static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager, struct gui *ui) { GtkActionGroup *action_group; GError *error = 0; action_group = gtk_action_group_new("Menu"); gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui); gtk_ui_manager_insert_action_group(ui_manager, action_group, 0); gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error); gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager)); return gtk_ui_manager_get_widget(ui_manager, "/MainMenu"); } void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar, GtkWidget *vbox, GtkUIManager *ui_manager) { gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); } static void combo_entry_changed(GtkComboBox *box, gpointer data) { struct gui_entry *ge = (struct gui_entry *) data; gint index; index = gtk_combo_box_get_active(box); multitext_set_entry(&ge->eta.iotype, index); multitext_set_entry(&ge->eta.bs, index); multitext_set_entry(&ge->eta.ioengine, index); multitext_set_entry(&ge->eta.iodepth, index); } static void combo_entry_destroy(GtkWidget *widget, gpointer data) { struct gui_entry *ge = (struct gui_entry *) data; multitext_free(&ge->eta.iotype); multitext_free(&ge->eta.bs); multitext_free(&ge->eta.ioengine); multitext_free(&ge->eta.iodepth); } static GtkWidget *new_client_page(struct gui_entry *ge) { GtkWidget *main_vbox, *probe, *probe_frame, *probe_box; GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox; main_vbox = gtk_vbox_new(FALSE, 3); top_align = gtk_alignment_new(0, 0, 1, 0); top_vbox = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(top_align), top_vbox); gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0); probe = gtk_frame_new("Job"); gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3); probe_frame = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(probe), probe_frame); probe_box = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3); ge->probe.hostname = new_info_label_in_frame(probe_box, "Host"); ge->probe.os = new_info_label_in_frame(probe_box, "OS"); ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture"); ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version"); probe_box = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3); ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs"); g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge); g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge); ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO"); ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)"); ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine"); ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth"); ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs"); ge->eta.files = new_info_entry_in_frame(probe_box, "Open files"); probe_box = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3); ge->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B); ge->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B); ge->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B); ge->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B); ge->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B); ge->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B); /* * Only add this if we have a commit rate */ #if 0 probe_box = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3); ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW"); ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS"); ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW"); ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS"); #endif /* * Set up a drawing area and IOPS and bandwidth graphs */ ge->graphs.drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area), DRAWING_AREA_XDIM, DRAWING_AREA_YDIM); gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow); g_signal_connect(G_OBJECT(ge->graphs.drawing_area), GFIO_DRAW_EVENT, G_CALLBACK(on_expose_drawing_area), &ge->graphs); g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event", G_CALLBACK(on_config_drawing_area), &ge->graphs); scrolled_window = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window), ge->graphs.drawing_area); gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0); setup_graphs(&ge->graphs); /* * Set up alignments for widgets at the bottom of ui, * align bottom left, expand horizontally but not vertically */ bottom_align = gtk_alignment_new(0, 1, 1, 0); ge->buttonbox = gtk_hbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox); gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0); add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist)); /* * Set up thread status progress bar */ ge->thread_status_pb = gtk_progress_bar_new(); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections"); gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb); return main_vbox; } static GtkWidget *new_main_page(struct gui *ui) { GtkWidget *main_vbox, *probe, *probe_frame, *probe_box; GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox; main_vbox = gtk_vbox_new(FALSE, 3); /* * Set up alignments for widgets at the top of ui, * align top left, expand horizontally but not vertically */ top_align = gtk_alignment_new(0, 0, 1, 0); top_vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(top_align), top_vbox); gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0); probe = gtk_frame_new("Run statistics"); gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3); probe_frame = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(probe), probe_frame); probe_box = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3); ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running"); ui->eta.read_bw = new_info_entry_in_frame_rgb(probe_box, "Read BW", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B); ui->eta.read_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_READ_R, GFIO_READ_G, GFIO_READ_B); ui->eta.write_bw = new_info_entry_in_frame_rgb(probe_box, "Write BW", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B); ui->eta.write_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_WRITE_R, GFIO_WRITE_G, GFIO_WRITE_B); ui->eta.trim_bw = new_info_entry_in_frame_rgb(probe_box, "Trim BW", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B); ui->eta.trim_iops = new_info_entry_in_frame_rgb(probe_box, "IOPS", GFIO_TRIM_R, GFIO_TRIM_G, GFIO_TRIM_B); /* * Only add this if we have a commit rate */ #if 0 probe_box = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3); ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW"); ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS"); ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW"); ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS"); #endif /* * Set up a drawing area and IOPS and bandwidth graphs */ ui->graphs.drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area), DRAWING_AREA_XDIM, DRAWING_AREA_YDIM); gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_lightyellow); g_signal_connect(G_OBJECT(ui->graphs.drawing_area), GFIO_DRAW_EVENT, G_CALLBACK(on_expose_drawing_area), &ui->graphs); g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event", G_CALLBACK(on_config_drawing_area), &ui->graphs); scrolled_window = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window), ui->graphs.drawing_area); gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0); setup_graphs(&ui->graphs); /* * Set up alignments for widgets at the bottom of ui, * align bottom left, expand horizontally but not vertically */ bottom_align = gtk_alignment_new(0, 1, 1, 0); ui->buttonbox = gtk_hbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox); gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0); /* * Set up thread status progress bar */ ui->thread_status_pb = gtk_progress_bar_new(); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections"); gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb); return main_vbox; } static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget, guint page, gpointer data) { struct gui *ui = (struct gui *) data; struct gui_entry *ge; if (!page) { set_job_menu_visible(ui, 0); set_view_results_visible(ui, 0); return TRUE; } set_job_menu_visible(ui, 1); ge = get_ge_from_page(ui, page, NULL); if (ge) update_button_states(ui, ge); return TRUE; } static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b) { time_t time_a = gtk_recent_info_get_visited(a); time_t time_b = gtk_recent_info_get_visited(b); return time_b - time_a; } static void add_recent_file_items(struct gui *ui) { const gchar *gfio = g_get_application_name(); GList *items, *item; int i = 0; if (ui->recent_ui_id) { gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id); gtk_ui_manager_ensure_update(ui->uimanager); } ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager); if (ui->actiongroup) { gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup); g_object_unref(ui->actiongroup); } ui->actiongroup = gtk_action_group_new("RecentFileActions"); gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1); items = gtk_recent_manager_get_items(ui->recentmanager); items = g_list_sort(items, (GCompareFunc) compare_recent_items); for (item = items; item && item->data; item = g_list_next(item)) { GtkRecentInfo *info = (GtkRecentInfo *) item->data; gchar *action_name; const gchar *label; GtkAction *action; if (!gtk_recent_info_has_application(info, gfio)) continue; /* * We only support local files for now */ if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info)) continue; action_name = g_strdup_printf("RecentFile%u", i++); label = gtk_recent_info_get_display_name(info); action = g_object_new(GTK_TYPE_ACTION, "name", action_name, "label", label, NULL); g_object_set_data_full(G_OBJECT(action), "gtk-recent-info", gtk_recent_info_ref(info), (GDestroyNotify) gtk_recent_info_unref); g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui); gtk_action_group_add_action(ui->actiongroup, action); g_object_unref(action); gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id, "/MainMenu/FileMenu/FileRecentFiles", label, action_name, GTK_UI_MANAGER_MENUITEM, FALSE); g_free(action_name); if (i == 8) break; } g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL); g_list_free(items); } static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx, gint x, gint y, GtkSelectionData *seldata, guint info, guint time, gpointer *data) { struct gui *ui = (struct gui *) data; gchar **uris; GtkWidget *source; source = gtk_drag_get_source_widget(ctx); if (source && widget == gtk_widget_get_toplevel(source)) { gtk_drag_finish(ctx, FALSE, FALSE, time); return; } uris = gtk_selection_data_get_uris(seldata); if (!uris) { gtk_drag_finish(ctx, FALSE, FALSE, time); return; } if (uris[0]) do_file_open_with_tab(ui, uris[0]); gtk_drag_finish(ctx, TRUE, FALSE, time); g_strfreev(uris); } static void init_ui(int *argc, char **argv[], struct gui *ui) { GtkSettings *settings; GtkWidget *vbox; /* Magical g*thread incantation, you just need this thread stuff. * Without it, the update that happens in gfio_update_thread_status * doesn't really happen in a timely fashion, you need expose events */ #if !GTK_CHECK_VERSION(2, 24, 0) if (!g_thread_supported()) g_thread_init(NULL); #endif gdk_threads_init(); gtk_init(argc, argv); settings = gtk_settings_get_default(); gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting"); g_type_init(); gdk_color_parse("#fffff4", &gfio_color_lightyellow); gdk_color_parse("white", &gfio_color_white); ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(ui->window), "fio"); gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768); g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui); g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui); ui->vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox); ui->uimanager = gtk_ui_manager_new(); ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui); gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager); ui->recentmanager = gtk_recent_manager_get_default(); add_recent_file_items(ui); ui->notebook = gtk_notebook_new(); g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui); gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1); gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook)); gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook); vbox = new_main_page(ui); gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY); gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window)); g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui); gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main")); gfio_ui_setup_log(ui); gtk_widget_show_all(ui->window); } int main(int argc, char *argv[], char *envp[]) { if (initialize_fio(envp)) return 1; if (fio_init_options()) return 1; gopt_init(); memset(&main_ui, 0, sizeof(main_ui)); main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal); init_ui(&argc, &argv, &main_ui); gdk_threads_enter(); gtk_main(); gdk_threads_leave(); g_hash_table_destroy(main_ui.ge_hash); gopt_exit(); return 0; } fio-2.1.3/gfio.h000066400000000000000000000066151222032232000133630ustar00rootroot00000000000000#ifndef GFIO_H #define GFIO_H #include #include "gcompat.h" #include "stat.h" #include "thread_options.h" #include "ghelpers.h" #include "graph.h" struct probe_widget { GtkWidget *hostname; GtkWidget *os; GtkWidget *arch; GtkWidget *fio_ver; }; struct eta_widget { GtkWidget *names; struct multitext_widget iotype; struct multitext_widget bs; struct multitext_widget ioengine; struct multitext_widget iodepth; GtkWidget *jobs; GtkWidget *files; GtkWidget *read_bw; GtkWidget *read_iops; GtkWidget *cr_bw; GtkWidget *cr_iops; GtkWidget *write_bw; GtkWidget *write_iops; GtkWidget *cw_bw; GtkWidget *cw_iops; GtkWidget *trim_bw; GtkWidget *trim_iops; }; struct gfio_graphs { #define DRAWING_AREA_XDIM 1000 #define DRAWING_AREA_YDIM 400 GtkWidget *drawing_area; struct graph *iops_graph; graph_label_t read_iops; graph_label_t write_iops; graph_label_t trim_iops; struct graph *bandwidth_graph; graph_label_t read_bw; graph_label_t write_bw; graph_label_t trim_bw; }; /* * Main window widgets and data */ struct gui { GtkUIManager *uimanager; GtkRecentManager *recentmanager; GtkActionGroup *actiongroup; guint recent_ui_id; GtkWidget *menu; GtkWidget *window; GtkWidget *vbox; GtkWidget *thread_status_pb; GtkWidget *buttonbox; GtkWidget *notebook; GtkWidget *error_info_bar; GtkWidget *error_label; GtkListStore *log_model; GtkWidget *log_tree; GtkWidget *log_view; struct gfio_graphs graphs; struct probe_widget probe; struct eta_widget eta; pthread_t server_t; pthread_t t; int handler_running; GHashTable *ge_hash; } main_ui; enum { GE_STATE_NEW = 1, GE_STATE_CONNECTED, GE_STATE_JOB_SENT, GE_STATE_JOB_STARTED, GE_STATE_JOB_RUNNING, GE_STATE_JOB_DONE, }; enum { GFIO_BUTTON_CONNECT = 0, GFIO_BUTTON_SEND, GFIO_BUTTON_START, GFIO_BUTTON_NR, }; /* * Notebook entry */ struct gui_entry { struct gui *ui; GtkWidget *vbox; GtkWidget *job_notebook; GtkWidget *thread_status_pb; GtkWidget *buttonbox; GtkWidget *button[GFIO_BUTTON_NR]; GtkWidget *notebook; GtkWidget *error_info_bar; GtkWidget *error_label; GtkWidget *results_window; GtkWidget *results_notebook; GtkUIManager *results_uimanager; GtkWidget *results_menu; GtkWidget *disk_util_vbox; GtkListStore *log_model; GtkWidget *log_tree; GtkWidget *log_view; struct gfio_graphs graphs; struct probe_widget probe; struct eta_widget eta; GtkWidget *page_label; gint page_num; unsigned int state; struct graph *clat_graph; struct graph *lat_bucket_graph; struct gfio_client *client; char *job_file; char *host; int port; int type; int server_start; }; struct end_results { struct group_run_stats gs; struct thread_stat ts; }; struct gfio_client_options { struct flist_head list; struct thread_options o; }; struct gfio_client { struct gui_entry *ge; struct fio_client *client; GtkWidget *err_entry; uint32_t client_cpus; uint64_t client_flags; struct flist_head o_list; unsigned int o_list_nr; struct end_results *results; unsigned int nr_results; uint32_t update_job_status; volatile uint32_t update_job_done; struct cmd_du_pdu *du; unsigned int nr_du; }; #define GFIO_MIME "text/fio" extern void gfio_view_log(struct gui *ui); extern void gfio_set_state(struct gui_entry *ge, unsigned int state); extern void clear_ge_ui_info(struct gui_entry *ge); extern const char *gfio_graph_font; extern GdkColor gfio_color_white; extern GdkColor gfio_color_lightyellow; #endif fio-2.1.3/ghelpers.c000066400000000000000000000115461222032232000142420ustar00rootroot00000000000000#include #include #include #include "gcompat.h" #include "ghelpers.h" GtkWidget *new_combo_entry_in_frame(GtkWidget *box, const char *label) { GtkWidget *entry, *frame; frame = gtk_frame_new(label); entry = gtk_combo_box_text_new(); gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3); gtk_container_add(GTK_CONTAINER(frame), entry); return entry; } GtkWidget *new_info_entry_in_frame(GtkWidget *box, const char *label) { GtkWidget *entry, *frame; frame = gtk_frame_new(label); entry = gtk_entry_new(); gtk_editable_set_editable(GTK_EDITABLE(entry), 0); gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3); gtk_container_add(GTK_CONTAINER(frame), entry); return entry; } static void fill_color_from_rgb(GdkColor *c, gfloat r, gfloat g, gfloat b) { gint R, G, B; gchar tmp[8]; memset(c, 0, sizeof(*c)); R = r * 255; G = g * 255; B = b * 255; snprintf(tmp, sizeof(tmp), "#%02x%02x%02x", R, G, B); gdk_color_parse(tmp, c); } GtkWidget *new_info_entry_in_frame_rgb(GtkWidget *box, const char *label, gfloat r, gfloat g, gfloat b) { GtkWidget *entry; GdkColor c; entry = new_info_entry_in_frame(box, label); fill_color_from_rgb(&c, r, g, b); gtk_widget_modify_text(entry, GTK_STATE_NORMAL, &c); return entry; } GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label) { GtkWidget *label_widget; GtkWidget *frame; frame = gtk_frame_new(label); label_widget = gtk_label_new(NULL); gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3); gtk_container_add(GTK_CONTAINER(frame), label_widget); return label_widget; } GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval) { GtkWidget *button, *box; box = gtk_hbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(hbox), box); button = gtk_spin_button_new_with_range(min, max, 1.0); gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0); gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID); gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval); return button; } void label_set_int_value(GtkWidget *entry, unsigned int val) { char tmp[80]; sprintf(tmp, "%u", val); gtk_label_set_text(GTK_LABEL(entry), tmp); } void entry_set_int_value(GtkWidget *entry, unsigned int val) { char tmp[80]; sprintf(tmp, "%u", val); gtk_entry_set_text(GTK_ENTRY(entry), tmp); } GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, unsigned int flags) { GtkCellRenderer *renderer; GtkTreeViewColumn *col; double xalign = 0.0; /* left as default */ PangoAlignment align; gboolean visible; align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT : (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_CENTER; visible = !(flags & INVISIBLE); renderer = gtk_cell_renderer_text_new(); col = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(col, title); if (!(flags & UNSORTABLE)) gtk_tree_view_column_set_sort_column_id(col, index); gtk_tree_view_column_set_resizable(col, TRUE); gtk_tree_view_column_pack_start(col, renderer, TRUE); gtk_tree_view_column_set_expand(col, TRUE); gtk_tree_view_column_add_attribute(col, renderer, "text", index); g_object_set(G_OBJECT(renderer), "alignment", align, NULL); switch (align) { case PANGO_ALIGN_LEFT: xalign = 0.0; break; case PANGO_ALIGN_CENTER: xalign = 0.5; break; case PANGO_ALIGN_RIGHT: xalign = 1.0; break; } gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), xalign, 0.5); gtk_tree_view_column_set_visible(col, visible); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), col); return col; } void multitext_add_entry(struct multitext_widget *mt, const char *text) { mt->text = realloc(mt->text, (mt->max_text + 1) * sizeof(char *)); mt->text[mt->max_text] = strdup(text); mt->max_text++; } void multitext_set_entry(struct multitext_widget *mt, unsigned int index) { if (index >= mt->max_text) return; if (!mt->text || !mt->text[index]) return; mt->cur_text = index; gtk_entry_set_text(GTK_ENTRY(mt->entry), mt->text[index]); } void multitext_update_entry(struct multitext_widget *mt, unsigned int index, const char *text) { if (!mt->text) return; if (mt->text[index]) free(mt->text[index]); mt->text[index] = strdup(text); if (mt->cur_text == index) gtk_entry_set_text(GTK_ENTRY(mt->entry), mt->text[index]); } void multitext_free(struct multitext_widget *mt) { int i; gtk_entry_set_text(GTK_ENTRY(mt->entry), ""); for (i = 0; i < mt->max_text; i++) { if (mt->text[i]) free(mt->text[i]); } free(mt->text); mt->cur_text = -1; mt->max_text = 0; } GtkWidget *get_scrolled_window(gint border_width) { GtkWidget *scroll; scroll = gtk_scrolled_window_new(NULL, NULL); gtk_container_set_border_width(GTK_CONTAINER(scroll), border_width); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); return scroll; } fio-2.1.3/ghelpers.h000066400000000000000000000023411222032232000142400ustar00rootroot00000000000000#ifndef GFIO_HELPERS_H #define GFIO_HELPERS_H GtkWidget *new_combo_entry_in_frame(GtkWidget *box, const char *label); GtkWidget *new_info_entry_in_frame(GtkWidget *box, const char *label); GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label); GtkWidget *new_info_entry_in_frame_rgb(GtkWidget *box, const char *label, gfloat r, gfloat g, gfloat b); GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval); void label_set_int_value(GtkWidget *entry, unsigned int val); void entry_set_int_value(GtkWidget *entry, unsigned int val); GtkWidget *get_scrolled_window(gint border_width); struct multitext_widget { GtkWidget *entry; char **text; unsigned int cur_text; unsigned int max_text; }; void multitext_add_entry(struct multitext_widget *mt, const char *text); void multitext_set_entry(struct multitext_widget *mt, unsigned int index); void multitext_update_entry(struct multitext_widget *mt, unsigned int index, const char *text); void multitext_free(struct multitext_widget *mt); #define ALIGN_LEFT 1 #define ALIGN_RIGHT 2 #define INVISIBLE 4 #define UNSORTABLE 8 GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, unsigned int flags); #endif fio-2.1.3/goptions.c000066400000000000000000001131751222032232000142740ustar00rootroot00000000000000#include #include #include #include #include #include #include "fio.h" #include "gfio.h" #include "ghelpers.h" #include "gerror.h" #include "parse.h" struct gopt { GtkWidget *box; unsigned int opt_index; unsigned int opt_type; gulong sig_handler; struct gopt_job_view *gjv; struct flist_head changed_list; }; struct gopt_combo { struct gopt gopt; GtkWidget *combo; }; struct gopt_int { struct gopt gopt; unsigned long long lastval; GtkWidget *spin; }; struct gopt_bool { struct gopt gopt; GtkWidget *check; }; struct gopt_str { struct gopt gopt; GtkWidget *entry; }; struct gopt_str_val { struct gopt gopt; GtkWidget *spin; GtkWidget *combo; unsigned int maxindex; }; #define GOPT_RANGE_SPIN 4 struct gopt_range { struct gopt gopt; GtkWidget *spins[GOPT_RANGE_SPIN]; }; struct gopt_str_multi { struct gopt gopt; GtkWidget *checks[PARSE_MAX_VP]; }; enum { GOPT_COMBO_INT = 1, GOPT_COMBO_STR, GOPT_INT, GOPT_BOOL, GOPT_STR, GOPT_STR_VAL, GOPT_RANGE, GOPT_STR_MULTI, }; struct gopt_frame_widget { GtkWidget *vbox[2]; unsigned int nr; }; struct gopt_job_view { struct gopt_frame_widget g_widgets[__FIO_OPT_G_NR]; GtkWidget *vboxes[__FIO_OPT_C_NR]; struct gopt *gopts[FIO_MAX_OPTS]; GtkWidget *dialog; GtkWidget *job_combo; struct gfio_client *client; struct flist_head changed_list; struct thread_options *o; int in_job_switch; }; static GNode *gopt_dep_tree; static GtkWidget *gopt_get_group_frame(struct gopt_job_view *gjv, GtkWidget *box, unsigned int groupmask) { unsigned int mask, group; struct opt_group *og; GtkWidget *frame, *hbox; struct gopt_frame_widget *gfw; if (!groupmask) return 0; mask = groupmask; og = opt_group_cat_from_mask(&mask); if (!og) return NULL; group = ffz(~groupmask); gfw = &gjv->g_widgets[group]; if (!gfw->vbox[0]) { frame = gtk_frame_new(og->name); gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 3); hbox = gtk_hbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(frame), hbox); gfw->vbox[0] = gtk_vbox_new(TRUE, 5); gfw->vbox[1] = gtk_vbox_new(TRUE, 5); gtk_box_pack_start(GTK_BOX(hbox), gfw->vbox[0], TRUE, TRUE, 5); gtk_box_pack_start(GTK_BOX(hbox), gfw->vbox[1], TRUE, TRUE, 5); } hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(gfw->vbox[gfw->nr++ & 1]), hbox, FALSE, FALSE, 5); return hbox; } /* * Mark children as invisible, if needed. */ static void gopt_set_children_visible(struct gopt_job_view *gjv, struct fio_option *parent, gboolean visible) { GNode *child, *node; if (parent->hide_on_set) visible = !visible; node = g_node_find(gopt_dep_tree, G_IN_ORDER, G_TRAVERSE_ALL, parent); child = g_node_first_child(node); while (child) { struct fio_option *o = child->data; struct gopt *g = o->gui_data; GtkWidget *widget = g->box; /* * Recurse into child, if it also has children */ if (g_node_n_children(child)) gopt_set_children_visible(gjv, o, visible); gtk_widget_set_sensitive(widget, visible); child = g_node_next_sibling(child); } } static void gopt_mark_index(struct gopt_job_view *gjv, struct gopt *gopt, unsigned int idx, int type) { INIT_FLIST_HEAD(&gopt->changed_list); assert(!gjv->gopts[idx]); gopt->opt_index = idx; gopt->opt_type = type; gopt->gjv = gjv; gjv->gopts[idx] = gopt; } static void gopt_dialog_update_apply_button(struct gopt_job_view *gjv) { GtkDialog *dialog = GTK_DIALOG(gjv->dialog); gboolean set; set = !flist_empty(&gjv->changed_list); gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_APPLY, set); if (set) { gtk_widget_set_sensitive(gjv->job_combo, 0); gtk_widget_set_tooltip_text(gjv->job_combo, "Apply option changes before switching to a new job"); } else { gtk_widget_set_sensitive(gjv->job_combo, 1); gtk_widget_set_tooltip_text(gjv->job_combo, "Change current job"); } } static void gopt_changed(struct gopt *gopt) { struct gopt_job_view *gjv = gopt->gjv; if (gjv->in_job_switch) return; /* * Add to changed list. This also prevents the option from being * freed when the widget is destroyed. */ if (flist_empty(&gopt->changed_list)) { flist_add_tail(&gopt->changed_list, &gjv->changed_list); gopt_dialog_update_apply_button(gjv); } } static void gopt_str_changed(GtkEntry *entry, gpointer data) { struct gopt_str *s = (struct gopt_str *) data; struct fio_option *o = &fio_options[s->gopt.opt_index]; const gchar *text; int set; gopt_changed(&s->gopt); text = gtk_entry_get_text(GTK_ENTRY(s->entry)); set = strcmp(text, "") != 0; gopt_set_children_visible(s->gopt.gjv, o, set); } static void gopt_str_destroy(GtkWidget *w, gpointer data) { struct gopt_str *s = (struct gopt_str *) data; free(s); gtk_widget_destroy(w); } static void gopt_str_store_set_val(struct gopt_str *s, const char *text) { if (text) gtk_entry_set_text(GTK_ENTRY(s->entry), text); } static struct gopt *gopt_new_str_store(struct gopt_job_view *gjv, struct fio_option *o, const char *text, unsigned int idx) { struct gopt_str *s; GtkWidget *label; s = calloc(1, sizeof(*s)); s->gopt.box = gtk_hbox_new(FALSE, 3); if (!o->lname) label = gtk_label_new(o->name); else label = gtk_label_new(o->lname); s->entry = gtk_entry_new(); gopt_mark_index(gjv, &s->gopt, idx, GOPT_STR); gtk_editable_set_editable(GTK_EDITABLE(s->entry), 1); if (text) gopt_str_store_set_val(s, text); else if (o->def) gopt_str_store_set_val(s, o->def); s->gopt.sig_handler = g_signal_connect(G_OBJECT(s->entry), "changed", G_CALLBACK(gopt_str_changed), s); g_signal_connect(G_OBJECT(s->entry), "destroy", G_CALLBACK(gopt_str_destroy), s); gtk_box_pack_start(GTK_BOX(s->gopt.box), s->entry, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(s->gopt.box), label, FALSE, FALSE, 0); return &s->gopt; } static void gopt_combo_changed(GtkComboBox *box, gpointer data) { struct gopt_combo *c = (struct gopt_combo *) data; struct fio_option *o = &fio_options[c->gopt.opt_index]; unsigned int index; gopt_changed(&c->gopt); index = gtk_combo_box_get_active(GTK_COMBO_BOX(c->combo)); gopt_set_children_visible(c->gopt.gjv, o, index); } static void gopt_combo_destroy(GtkWidget *w, gpointer data) { struct gopt_combo *c = (struct gopt_combo *) data; free(c); gtk_widget_destroy(w); } static struct gopt_combo *__gopt_new_combo(struct gopt_job_view *gjv, struct fio_option *o, unsigned int idx, int type) { struct gopt_combo *c; GtkWidget *label; c = calloc(1, sizeof(*c)); c->gopt.box = gtk_hbox_new(FALSE, 3); if (!o->lname) label = gtk_label_new(o->name); else label = gtk_label_new(o->lname); c->combo = gtk_combo_box_text_new(); gopt_mark_index(gjv, &c->gopt, idx, type); g_signal_connect(G_OBJECT(c->combo), "destroy", G_CALLBACK(gopt_combo_destroy), c); gtk_box_pack_start(GTK_BOX(c->gopt.box), c->combo, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(c->gopt.box), label, FALSE, FALSE, 0); return c; } static void gopt_combo_str_set_val(struct gopt_combo *c, const char *text) { struct fio_option *o = &fio_options[c->gopt.opt_index]; struct value_pair *vp; int i; i = 0; vp = &o->posval[0]; while (vp->ival) { if (!strcmp(vp->ival, text)) { gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), i); break; } vp++; i++; } } static struct gopt *gopt_new_combo_str(struct gopt_job_view *gjv, struct fio_option *o, const char *text, unsigned int idx) { struct gopt_combo *c; struct value_pair *vp; int i, active = 0; c = __gopt_new_combo(gjv, o, idx, GOPT_COMBO_STR); i = 0; vp = &o->posval[0]; while (vp->ival) { gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(c->combo), vp->ival); if (o->def && !strcmp(vp->ival, o->def)) active = i; vp++; i++; } gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), active); if (text) gopt_combo_str_set_val(c, text); c->gopt.sig_handler = g_signal_connect(G_OBJECT(c->combo), "changed", G_CALLBACK(gopt_combo_changed), c); return &c->gopt; } static void gopt_combo_int_set_val(struct gopt_combo *c, unsigned int ip) { struct fio_option *o = &fio_options[c->gopt.opt_index]; struct value_pair *vp; int i; i = 0; vp = &o->posval[0]; while (vp->ival) { if (vp->oval == ip) { gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), i); break; } vp++; i++; } } static struct gopt *gopt_new_combo_int(struct gopt_job_view *gjv, struct fio_option *o, unsigned int *ip, unsigned int idx) { struct gopt_combo *c; struct value_pair *vp; int i, active = 0; c = __gopt_new_combo(gjv, o, idx, GOPT_COMBO_INT); i = 0; vp = &o->posval[0]; while (vp->ival) { gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(c->combo), vp->ival); if (ip && vp->oval == *ip) active = i; vp++; i++; } gtk_combo_box_set_active(GTK_COMBO_BOX(c->combo), active); if (ip) gopt_combo_int_set_val(c, *ip); c->gopt.sig_handler = g_signal_connect(G_OBJECT(c->combo), "changed", G_CALLBACK(gopt_combo_changed), c); return &c->gopt; } static void gopt_str_multi_toggled(GtkToggleButton *button, gpointer data) { struct gopt_str_multi *m = (struct gopt_str_multi *) data; gopt_changed(&m->gopt); } static void gopt_str_multi_destroy(GtkWidget *w, gpointer data) { struct gopt_str_multi *m = (struct gopt_str_multi *) data; free(m); gtk_widget_destroy(w); } static void gopt_str_multi_set_val(struct gopt_str_multi *m, int val) { } static struct gopt *gopt_new_str_multi(struct gopt_job_view *gjv, struct fio_option *o, unsigned int idx) { struct gopt_str_multi *m; struct value_pair *vp; GtkWidget *frame, *hbox; int i; m = calloc(1, sizeof(*m)); m->gopt.box = gtk_hbox_new(FALSE, 3); gopt_mark_index(gjv, &m->gopt, idx, GOPT_STR_MULTI); if (!o->lname) frame = gtk_frame_new(o->name); else frame = gtk_frame_new(o->lname); gtk_box_pack_start(GTK_BOX(m->gopt.box), frame, FALSE, FALSE, 3); hbox = gtk_hbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), hbox); i = 0; vp = &o->posval[0]; while (vp->ival) { m->checks[i] = gtk_check_button_new_with_label(vp->ival); gtk_widget_set_tooltip_text(m->checks[i], vp->help); gtk_box_pack_start(GTK_BOX(hbox), m->checks[i], FALSE, FALSE, 3); g_signal_connect(G_OBJECT(m->checks[i]), "toggled", G_CALLBACK(gopt_str_multi_toggled), m); vp++; i++; } gopt_str_multi_set_val(m, 0); g_signal_connect(G_OBJECT(m->gopt.box), "destroy", G_CALLBACK(gopt_str_multi_destroy), m); return &m->gopt; } static void gopt_int_changed(GtkSpinButton *spin, gpointer data) { struct gopt_int *i = (struct gopt_int *) data; struct fio_option *o = &fio_options[i->gopt.opt_index]; GtkAdjustment *adj; int value, delta; gopt_changed(&i->gopt); adj = gtk_spin_button_get_adjustment(spin); value = gtk_adjustment_get_value(adj); delta = value - i->lastval; i->lastval = value; if (o->inv_opt) { struct gopt *b_inv = o->inv_opt->gui_data; struct gopt_int *i_inv = container_of(b_inv, struct gopt_int, gopt); int cur_val; assert(o->type == o->inv_opt->type); cur_val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(i_inv->spin)); cur_val -= delta; g_signal_handler_block(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler); gtk_spin_button_set_value(GTK_SPIN_BUTTON(i_inv->spin), cur_val); g_signal_handler_unblock(G_OBJECT(i_inv->spin), i_inv->gopt.sig_handler); } } static void gopt_int_destroy(GtkWidget *w, gpointer data) { struct gopt_int *i = (struct gopt_int *) data; free(i); gtk_widget_destroy(w); } static void gopt_int_set_val(struct gopt_int *i, unsigned long long p) { gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->spin), p); i->lastval = p; } static struct gopt_int *__gopt_new_int(struct gopt_job_view *gjv, struct fio_option *o, unsigned long long *p, unsigned int idx) { unsigned long long defval; struct gopt_int *i; guint maxval, interval; GtkWidget *label; i = calloc(1, sizeof(*i)); i->gopt.box = gtk_hbox_new(FALSE, 3); if (!o->lname) label = gtk_label_new(o->name); else label = gtk_label_new(o->lname); maxval = o->maxval; if (!maxval) maxval = UINT_MAX; defval = 0; if (p) defval = *p; else if (o->def) { long long val; check_str_bytes(o->def, &val, o); defval = val; } interval = 1.0; if (o->interval) interval = o->interval; i->spin = gtk_spin_button_new_with_range(o->minval, maxval, interval); gopt_mark_index(gjv, &i->gopt, idx, GOPT_INT); gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(i->spin), GTK_UPDATE_IF_VALID); if (p) gopt_int_set_val(i, *p); else gopt_int_set_val(i, defval); i->gopt.sig_handler = g_signal_connect(G_OBJECT(i->spin), "value-changed", G_CALLBACK(gopt_int_changed), i); g_signal_connect(G_OBJECT(i->spin), "destroy", G_CALLBACK(gopt_int_destroy), i); gtk_box_pack_start(GTK_BOX(i->gopt.box), i->spin, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(i->gopt.box), label, FALSE, FALSE, 0); return i; } static struct gopt *gopt_new_int(struct gopt_job_view *gjv, struct fio_option *o, unsigned int *ip, unsigned int idx) { unsigned long long ullp; struct gopt_int *i; if (ip) { ullp = *ip; i = __gopt_new_int(gjv, o, &ullp, idx); } else i = __gopt_new_int(gjv, o, NULL, idx); return &i->gopt; } static struct gopt *gopt_new_ullong(struct gopt_job_view *gjv, struct fio_option *o, unsigned long long *p, unsigned int idx) { struct gopt_int *i; i = __gopt_new_int(gjv, o, p, idx); return &i->gopt; } static void gopt_bool_toggled(GtkToggleButton *button, gpointer data) { struct gopt_bool *b = (struct gopt_bool *) data; struct fio_option *o = &fio_options[b->gopt.opt_index]; gboolean set; gopt_changed(&b->gopt); set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b->check)); if (o->inv_opt) { struct gopt *g_inv = o->inv_opt->gui_data; struct gopt_bool *b_inv = container_of(g_inv, struct gopt_bool, gopt); assert(o->type == o->inv_opt->type); g_signal_handler_block(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b_inv->check), !set); g_signal_handler_unblock(G_OBJECT(b_inv->check), b_inv->gopt.sig_handler); } gopt_set_children_visible(b->gopt.gjv, o, set); } static void gopt_bool_destroy(GtkWidget *w, gpointer data) { struct gopt_bool *b = (struct gopt_bool *) data; free(b); gtk_widget_destroy(w); } static void gopt_bool_set_val(struct gopt_bool *b, unsigned int val) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->check), val); } static struct gopt *gopt_new_bool(struct gopt_job_view *gjv, struct fio_option *o, unsigned int *val, unsigned int idx) { struct gopt_bool *b; GtkWidget *label; int defstate = 0; b = calloc(1, sizeof(*b)); b->gopt.box = gtk_hbox_new(FALSE, 3); if (!o->lname) label = gtk_label_new(o->name); else label = gtk_label_new(o->lname); b->check = gtk_check_button_new(); gopt_mark_index(gjv, &b->gopt, idx, GOPT_BOOL); if (o->def && !strcmp(o->def, "1")) defstate = 1; if (o->neg) defstate = !defstate; if (val) gopt_bool_set_val(b, *val); else gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->check), defstate); b->gopt.sig_handler = g_signal_connect(G_OBJECT(b->check), "toggled", G_CALLBACK(gopt_bool_toggled), b); g_signal_connect(G_OBJECT(b->check), "destroy", G_CALLBACK(gopt_bool_destroy), b); gtk_box_pack_start(GTK_BOX(b->gopt.box), b->check, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(b->gopt.box), label, FALSE, FALSE, 0); return &b->gopt; } /* * These are paired 0/1 and 2/3. 0/2 are min values, 1/3 are max values. * If the max is made smaller than min, adjust min down. * If the min is made larger than max, adjust the max. */ static void range_value_changed(GtkSpinButton *spin, gpointer data) { struct gopt_range *r = (struct gopt_range *) data; int changed = -1, i; gint val, mval; gopt_changed(&r->gopt); for (i = 0; i < GOPT_RANGE_SPIN; i++) { if (GTK_SPIN_BUTTON(r->spins[i]) == spin) { changed = i; break; } } assert(changed != -1); /* * Min changed */ if (changed == 0 || changed == 2) { GtkWidget *mspin = r->spins[changed + 1]; val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed])); mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin)); if (val > mval) gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val); } else { GtkWidget *mspin = r->spins[changed - 1]; val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[changed])); mval = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mspin)); if (val < mval) gtk_spin_button_set_value(GTK_SPIN_BUTTON(mspin), val); } } static void gopt_range_destroy(GtkWidget *w, gpointer data) { struct gopt_range *r = (struct gopt_range *) data; free(r); gtk_widget_destroy(w); } static void gopt_int_range_set_val(struct gopt_range *r, unsigned int *vals) { int i; for (i = 0; i < GOPT_RANGE_SPIN; i++) gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->spins[i]), vals[i]); } static struct gopt *gopt_new_int_range(struct gopt_job_view *gjv, struct fio_option *o, unsigned int **ip, unsigned int idx) { struct gopt_range *r; GtkWidget *label; guint interval; unsigned int defvals[GOPT_RANGE_SPIN]; gint maxval; int i; r = calloc(1, sizeof(*r)); r->gopt.box = gtk_hbox_new(FALSE, 3); gopt_mark_index(gjv, &r->gopt, idx, GOPT_RANGE); if (!o->lname) label = gtk_label_new(o->name); else label = gtk_label_new(o->lname); maxval = o->maxval; if (!maxval) maxval = INT_MAX; memset(defvals, 0, sizeof(defvals)); if (o->def) { long long val; check_str_bytes(o->def, &val, o); for (i = 0; i < GOPT_RANGE_SPIN; i++) defvals[i] = val; } interval = 1.0; if (o->interval) interval = o->interval; for (i = 0; i < GOPT_RANGE_SPIN; i++) { r->spins[i] = gtk_spin_button_new_with_range(o->minval, maxval, interval); gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(r->spins[i]), GTK_UPDATE_IF_VALID); gtk_box_pack_start(GTK_BOX(r->gopt.box), r->spins[i], FALSE, FALSE, 0); } if (ip) gopt_int_range_set_val(r, *ip); else gopt_int_range_set_val(r, defvals); for (i = 0; i < GOPT_RANGE_SPIN; i++) g_signal_connect(G_OBJECT(r->spins[i]), "value-changed", G_CALLBACK(range_value_changed), r); gtk_box_pack_start(GTK_BOX(r->gopt.box), label, FALSE, FALSE, 0); g_signal_connect(G_OBJECT(r->gopt.box), "destroy", G_CALLBACK(gopt_range_destroy), r); return &r->gopt; } static void gopt_str_val_destroy(GtkWidget *w, gpointer data) { struct gopt_str_val *g = (struct gopt_str_val *) data; free(g); gtk_widget_destroy(w); } static void gopt_str_val_spin_wrapped(GtkSpinButton *spin, gpointer data) { struct gopt_str_val *g = (struct gopt_str_val *) data; unsigned int val; GtkAdjustment *adj; gint index; adj = gtk_spin_button_get_adjustment(spin); val = gtk_adjustment_get_value(adj); /* * Can't rely on exact value, as fast changes increment >= 1 */ if (!val) { index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo)); if (index + 1 <= g->maxindex) { val = 1; gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), ++index); } else val = 1023; gtk_spin_button_set_value(spin, val); } else { index = gtk_combo_box_get_active(GTK_COMBO_BOX(g->combo)); if (index) { gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), --index); gtk_spin_button_set_value(spin, 1023); } else gtk_spin_button_set_value(spin, 0); } } static void gopt_str_val_changed(GtkSpinButton *spin, gpointer data) { struct gopt_str_val *g = (struct gopt_str_val *) data; gopt_changed(&g->gopt); } static void gopt_str_val_set_val(struct gopt_str_val *g, unsigned long long val) { int i = 0; do { if (!val || (val % 1024)) break; i++; val /= 1024; } while (1); gtk_spin_button_set_value(GTK_SPIN_BUTTON(g->spin), val); gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), i); } static struct gopt *gopt_new_str_val(struct gopt_job_view *gjv, struct fio_option *o, unsigned long long *p, unsigned int idx) { struct gopt_str_val *g; const gchar *postfix[] = { "B", "KB", "MB", "GB", "PB", "TB", "" }; GtkWidget *label; int i; g = calloc(1, sizeof(*g)); g->gopt.box = gtk_hbox_new(FALSE, 3); if (!o->lname) label = gtk_label_new(o->name); else label = gtk_label_new(o->lname); gopt_mark_index(gjv, &g->gopt, idx, GOPT_STR_VAL); g->spin = gtk_spin_button_new_with_range(0.0, 1023.0, 1.0); gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(g->spin), GTK_UPDATE_IF_VALID); gtk_spin_button_set_value(GTK_SPIN_BUTTON(g->spin), 0); gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(g->spin), 1); gtk_box_pack_start(GTK_BOX(g->gopt.box), g->spin, FALSE, FALSE, 0); g_signal_connect(G_OBJECT(g->spin), "wrapped", G_CALLBACK(gopt_str_val_spin_wrapped), g); g_signal_connect(G_OBJECT(g->spin), "changed", G_CALLBACK(gopt_str_val_changed), g); g->combo = gtk_combo_box_text_new(); i = 0; while (strlen(postfix[i])) { gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(g->combo), postfix[i]); i++; } g->maxindex = i - 1; gtk_combo_box_set_active(GTK_COMBO_BOX(g->combo), 0); gtk_box_pack_start(GTK_BOX(g->gopt.box), g->combo, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(g->gopt.box), label, FALSE, FALSE, 3); if (p) gopt_str_val_set_val(g, *p); g_signal_connect(G_OBJECT(g->combo), "changed", G_CALLBACK(gopt_str_val_changed), g); g_signal_connect(G_OBJECT(g->gopt.box), "destroy", G_CALLBACK(gopt_str_val_destroy), g); return &g->gopt; } static void gopt_set_option(struct gopt_job_view *gjv, struct fio_option *o, struct gopt *gopt, struct thread_options *to) { switch (o->type) { case FIO_OPT_STR_VAL: { unsigned long long *ullp = NULL; struct gopt_str_val *g; if (o->off1) ullp = td_var(to, o->off1); g = container_of(gopt, struct gopt_str_val, gopt); if (ullp) gopt_str_val_set_val(g, *ullp); break; } case FIO_OPT_STR_VAL_TIME: { unsigned long long *ullp = NULL; struct gopt_int *i; if (o->off1) ullp = td_var(to, o->off1); i = container_of(gopt, struct gopt_int, gopt); if (ullp) gopt_int_set_val(i, *ullp); break; } case FIO_OPT_INT: if (o->posval[0].ival) { unsigned int *ip = NULL; struct gopt_combo *c; if (o->off1) ip = td_var(to, o->off1); c = container_of(gopt, struct gopt_combo, gopt); if (ip) gopt_combo_int_set_val(c, *ip); } else { unsigned int *ip = NULL; struct gopt_int *i; if (o->off1) ip = td_var(to, o->off1); i = container_of(gopt, struct gopt_int, gopt); if (ip) gopt_int_set_val(i, *ip); } break; case FIO_OPT_STR_SET: case FIO_OPT_BOOL: { unsigned int *ip = NULL; struct gopt_bool *b; if (o->off1) ip = td_var(to, o->off1); b = container_of(gopt, struct gopt_bool, gopt); if (ip) gopt_bool_set_val(b, *ip); break; } case FIO_OPT_STR: { if (o->posval[0].ival) { unsigned int *ip = NULL; struct gopt_combo *c; if (o->off1) ip = td_var(to, o->off1); c = container_of(gopt, struct gopt_combo, gopt); if (ip) gopt_combo_int_set_val(c, *ip); } else { struct gopt_str *s; char *text = NULL; if (o->off1) { char **p = td_var(to, o->off1); text = *p; } s = container_of(gopt, struct gopt_str, gopt); gopt_str_store_set_val(s, text); } break; } case FIO_OPT_STR_STORE: { struct gopt_combo *c; char *text = NULL; if (o->off1) { char **p = td_var(to, o->off1); text = *p; } if (!o->posval[0].ival) { struct gopt_str *s; s = container_of(gopt, struct gopt_str, gopt); gopt_str_store_set_val(s, text); break; } c = container_of(gopt, struct gopt_combo, gopt); if (text) gopt_combo_str_set_val(c, text); break; } case FIO_OPT_STR_MULTI: /* HANDLE ME */ break; case FIO_OPT_RANGE: { struct gopt_range *r; unsigned int *ip[4] = { td_var(to, o->off1), td_var(to, o->off2), td_var(to, o->off3), td_var(to, o->off4) }; r = container_of(gopt, struct gopt_range, gopt); gopt_int_range_set_val(r, *ip); break; } /* still need to handle this one */ case FIO_OPT_FLOAT_LIST: break; case FIO_OPT_DEPRECATED: break; default: printf("ignore type %u\n", o->type); break; } } static void gopt_add_option(struct gopt_job_view *gjv, GtkWidget *hbox, struct fio_option *o, unsigned int opt_index, struct thread_options *to) { struct gopt *go = NULL; switch (o->type) { case FIO_OPT_STR_VAL: { unsigned long long *ullp = NULL; if (o->off1) ullp = td_var(to, o->off1); go = gopt_new_str_val(gjv, o, ullp, opt_index); break; } case FIO_OPT_STR_VAL_TIME: { unsigned long long *ullp = NULL; if (o->off1) ullp = td_var(to, o->off1); go = gopt_new_ullong(gjv, o, ullp, opt_index); break; } case FIO_OPT_INT: if (o->posval[0].ival) { unsigned int *ip = NULL; if (o->off1) ip = td_var(to, o->off1); go = gopt_new_combo_int(gjv, o, ip, opt_index); } else { unsigned int *ip = NULL; if (o->off1) ip = td_var(to, o->off1); go = gopt_new_int(gjv, o, ip, opt_index); } break; case FIO_OPT_STR_SET: case FIO_OPT_BOOL: { unsigned int *ip = NULL; if (o->off1) ip = td_var(to, o->off1); go = gopt_new_bool(gjv, o, ip, opt_index); break; } case FIO_OPT_STR: { if (o->posval[0].ival) { unsigned int *ip = NULL; if (o->off1) ip = td_var(to, o->off1); go = gopt_new_combo_int(gjv, o, ip, opt_index); } else { /* TODO: usually ->cb, or unsigned int pointer */ go = gopt_new_str_store(gjv, o, NULL, opt_index); } break; } case FIO_OPT_STR_STORE: { char *text = NULL; if (o->off1) { char **p = td_var(to, o->off1); text = *p; } if (!o->posval[0].ival) { go = gopt_new_str_store(gjv, o, text, opt_index); break; } go = gopt_new_combo_str(gjv, o, text, opt_index); break; } case FIO_OPT_STR_MULTI: go = gopt_new_str_multi(gjv, o, opt_index); break; case FIO_OPT_RANGE: { unsigned int *ip[4] = { td_var(to, o->off1), td_var(to, o->off2), td_var(to, o->off3), td_var(to, o->off4) }; go = gopt_new_int_range(gjv, o, ip, opt_index); break; } /* still need to handle this one */ case FIO_OPT_FLOAT_LIST: break; case FIO_OPT_DEPRECATED: break; default: printf("ignore type %u\n", o->type); break; } if (go) { GtkWidget *dest; if (o->help) gtk_widget_set_tooltip_text(go->box, o->help); o->gui_data = go; dest = gopt_get_group_frame(gjv, hbox, o->group); if (!dest) gtk_box_pack_start(GTK_BOX(hbox), go->box, FALSE, FALSE, 5); else gtk_box_pack_start(GTK_BOX(dest), go->box, FALSE, FALSE, 5); } } static void gopt_add_options(struct gopt_job_view *gjv, struct thread_options *to) { GtkWidget *hbox = NULL; int i; /* * First add all options */ for (i = 0; fio_options[i].name; i++) { struct fio_option *o = &fio_options[i]; unsigned int mask = o->category; struct opt_group *og; while ((og = opt_group_from_mask(&mask)) != NULL) { GtkWidget *vbox = gjv->vboxes[ffz(~og->mask)]; hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); gopt_add_option(gjv, hbox, o, i, to); } } } static void gopt_set_options(struct gopt_job_view *gjv, struct thread_options *to) { int i; for (i = 0; fio_options[i].name; i++) { struct fio_option *o = &fio_options[i]; struct gopt *gopt = gjv->gopts[i]; gopt_set_option(gjv, o, gopt, to); } } static GtkWidget *gopt_add_tab(GtkWidget *notebook, const char *name) { GtkWidget *box, *vbox, *scroll; scroll = gtk_scrolled_window_new(NULL, NULL); gtk_container_set_border_width(GTK_CONTAINER(scroll), 5); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); vbox = gtk_vbox_new(FALSE, 3); box = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), vbox); gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scroll, gtk_label_new(name)); return vbox; } static GtkWidget *gopt_add_group_tab(GtkWidget *notebook, struct opt_group *og) { return gopt_add_tab(notebook, og->name); } static void gopt_add_group_tabs(GtkWidget *notebook, struct gopt_job_view *gjv) { struct opt_group *og; unsigned int i; i = 0; do { unsigned int mask = (1U << i); og = opt_group_from_mask(&mask); if (!og) break; gjv->vboxes[i] = gopt_add_group_tab(notebook, og); i++; } while (1); } static void gopt_handle_str_multi_changed(struct gopt_job_view *gjv, struct gopt_str_multi *m, struct fio_option *o) { unsigned int *ip = td_var(gjv->o, o->off1); struct value_pair *vp; gboolean set; guint val = 0; int i; i = 0; vp = &o->posval[0]; while (vp->ival) { if (!m->checks[i]) break; set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m->checks[i])); if (set) { if (vp->or) val |= vp->oval; else val = vp->oval; } i++; vp++; } if (o->off1) *ip = val; } static void gopt_handle_range_changed(struct gopt_job_view *gjv, struct gopt_range *r, struct fio_option *o) { unsigned int *ip[4] = { td_var(gjv->o, o->off1), td_var(gjv->o, o->off2), td_var(gjv->o, o->off3), td_var(gjv->o, o->off4) }; gint val; int i; for (i = 0; i < GOPT_RANGE_SPIN; i++) { val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(r->spins[i])); *ip[i] = val; } } static void gopt_handle_str_val_changed(struct gopt_job_view *gjv, struct gopt_str_val *s, struct fio_option *o) { unsigned long long *ullp = td_var(gjv->o, o->off1); GtkAdjustment *adj; gint index; if (!ullp) return; /* * Numerical value */ adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(s->spin)); *ullp = gtk_adjustment_get_value(adj); /* * Multiplier */ index = gtk_combo_box_get_active(GTK_COMBO_BOX(s->combo)); while (index--) *ullp *= 1024ULL; } static void gopt_handle_str_changed(struct gopt_job_view *gjv, struct gopt_str *s, struct fio_option *o) { char **p = td_var(gjv->o, o->off1); if (*p) free(*p); *p = strdup(gtk_entry_get_text(GTK_ENTRY(s->entry))); } static void gopt_handle_bool_changed(struct gopt_job_view *gjv, struct gopt_bool *b, struct fio_option *o) { unsigned int *ip = td_var(gjv->o, o->off1); gboolean set; set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(b->check)); *ip = set; } static void gopt_handle_int_changed(struct gopt_job_view *gjv, struct gopt_int *i, struct fio_option *o) { unsigned int *ip = td_var(gjv->o, o->off1); GtkAdjustment *adj; guint val; adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(i->spin)); val = gtk_adjustment_get_value(adj); *ip = val; } static void gopt_handle_combo_str_changed(struct gopt_job_view *gjv, struct gopt_combo *c, struct fio_option *o) { char **p = td_var(gjv->o, o->off1); if (*p) free(*p); *p = strdup(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(c->combo))); } static void gopt_handle_combo_int_changed(struct gopt_job_view *gjv, struct gopt_combo *c, struct fio_option *o) { unsigned int *ip = td_var(gjv->o, o->off1); gint index; index = gtk_combo_box_get_active(GTK_COMBO_BOX(c->combo)); *ip = o->posval[index].oval; } static void gopt_handle_changed(struct gopt *gopt) { struct fio_option *o = &fio_options[gopt->opt_index]; struct gopt_job_view *gjv = gopt->gjv; switch (gopt->opt_type) { case GOPT_COMBO_INT: { struct gopt_combo *c; c = container_of(gopt, struct gopt_combo, gopt); gopt_handle_combo_int_changed(gjv, c, o); break; } case GOPT_COMBO_STR: { struct gopt_combo *c; c = container_of(gopt, struct gopt_combo, gopt); gopt_handle_combo_str_changed(gjv, c, o); break; } case GOPT_INT: { struct gopt_int *i; i = container_of(gopt, struct gopt_int, gopt); gopt_handle_int_changed(gjv, i, o); break; } case GOPT_BOOL: { struct gopt_bool *b; b = container_of(gopt, struct gopt_bool, gopt); gopt_handle_bool_changed(gjv, b, o); break; } case GOPT_STR: { struct gopt_str *s; s = container_of(gopt, struct gopt_str, gopt); gopt_handle_str_changed(gjv, s, o); break; } case GOPT_STR_VAL: { struct gopt_str_val *s; s = container_of(gopt, struct gopt_str_val, gopt); gopt_handle_str_val_changed(gjv, s, o); break; } case GOPT_RANGE: { struct gopt_range *r; r = container_of(gopt, struct gopt_range, gopt); gopt_handle_range_changed(gjv, r, o); break; } case GOPT_STR_MULTI: { struct gopt_str_multi *m; m = container_of(gopt, struct gopt_str_multi, gopt); gopt_handle_str_multi_changed(gjv, m, o); break; } default: log_err("gfio: bad option type: %d\n", gopt->opt_type); break; } } static void gopt_report_update_status(struct gopt_job_view *gjv) { struct gfio_client *gc = gjv->client; char tmp[80]; sprintf(tmp, "\nCompleted with error: %d\n", gc->update_job_status); gfio_report_info(gc->ge->ui, "Update job", tmp); } static int gopt_handle_changed_options(struct gopt_job_view *gjv) { struct gfio_client *gc = gjv->client; struct flist_head *entry; uint64_t waitid = 0; struct gopt *gopt; int ret; flist_for_each(entry, &gjv->changed_list) { gopt = flist_entry(entry, struct gopt, changed_list); gopt_handle_changed(gopt); } gc->update_job_status = 0; gc->update_job_done = 0; ret = fio_client_update_options(gc->client, gjv->o, &waitid); if (ret) goto done; ret = fio_client_wait_for_reply(gc->client, waitid); if (ret) goto done; assert(gc->update_job_done); if (gc->update_job_status) goto done; while (!flist_empty(&gjv->changed_list)) { gopt = flist_entry(gjv->changed_list.next, struct gopt, changed_list); flist_del_init(&gopt->changed_list); } done: gopt_dialog_update_apply_button(gjv); return ret; } static gint gopt_dialog_cancel(gint response) { switch (response) { case GTK_RESPONSE_NONE: case GTK_RESPONSE_REJECT: case GTK_RESPONSE_DELETE_EVENT: case GTK_RESPONSE_CANCEL: case GTK_RESPONSE_NO: return 1; default: return 0; } } static gint gopt_dialog_done(gint response) { switch (response) { case GTK_RESPONSE_ACCEPT: case GTK_RESPONSE_OK: case GTK_RESPONSE_YES: return 1; default: return 0; } } static void gopt_handle_option_dialog(struct gopt_job_view *gjv) { gint response; do { response = gtk_dialog_run(GTK_DIALOG(gjv->dialog)); if (gopt_dialog_cancel(response) || gopt_dialog_done(response)) break; /* * Apply */ gopt_handle_changed_options(gjv); gopt_report_update_status(gjv); } while (1); if (gopt_dialog_cancel(response)) return; gopt_handle_changed_options(gjv); } static void gopt_job_changed(GtkComboBox *box, gpointer data) { struct gopt_job_view *gjv = (struct gopt_job_view *) data; struct gfio_client_options *gco = NULL; struct gfio_client *gc = gjv->client; struct flist_head *entry; gchar *job; /* * The switch act should be sensitized appropriately, so that we * never get here with modified options. */ if (!flist_empty(&gjv->changed_list)) { gfio_report_info(gc->ge->ui, "Internal Error", "Modified options on job switch.\nThat should not be possible!\n"); return; } job = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gjv->job_combo)); flist_for_each(entry, &gc->o_list) { const char *name; gco = flist_entry(entry, struct gfio_client_options, list); name = gco->o.name; if (!name || !strlen(name)) name = "Default job"; if (!strcmp(name, job)) break; gco = NULL; } if (!gco) { gfio_report_info(gc->ge->ui, "Internal Error", "Could not find job description.\nThat should not be possible!\n"); return; } gjv->in_job_switch = 1; gopt_set_options(gjv, &gco->o); gjv->in_job_switch = 0; } void gopt_get_options_window(GtkWidget *window, struct gfio_client *gc) { GtkWidget *dialog, *notebook, *vbox, *topvbox, *combo; struct gfio_client_options *gco; struct flist_head *entry; struct gopt_job_view *gjv; dialog = gtk_dialog_new_with_buttons("Fio options", GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_APPLY, GTK_RESPONSE_APPLY, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); combo = gtk_combo_box_text_new(); flist_for_each(entry, &gc->o_list) { struct thread_options *o; const char *name; gco = flist_entry(entry, struct gfio_client_options, list); o = &gco->o; name = o->name; if (!name || !strlen(name)) name = "Default job"; gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), name); } gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0); gtk_widget_set_size_request(GTK_WIDGET(dialog), 1024, 768); topvbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_box_pack_start(GTK_BOX(topvbox), combo, FALSE, FALSE, 5); vbox = gtk_vbox_new(TRUE, 5); gtk_box_pack_start(GTK_BOX(topvbox), vbox, TRUE, TRUE, 5); notebook = gtk_notebook_new(); gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), 1); gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook)); gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 5); gjv = calloc(1, sizeof(*gjv)); INIT_FLIST_HEAD(&gjv->changed_list); gco = flist_entry(gc->o_list.next, struct gfio_client_options, list); gjv->o = &gco->o; gjv->dialog = dialog; gjv->client = gc; gjv->job_combo = combo; gopt_add_group_tabs(notebook, gjv); gopt_add_options(gjv, &gco->o); gopt_dialog_update_apply_button(gjv); g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(gopt_job_changed), gjv); gtk_widget_show_all(dialog); gopt_handle_option_dialog(gjv); gtk_widget_destroy(dialog); free(gjv); } /* * Build n-ary option dependency tree */ void gopt_init(void) { int i; gopt_dep_tree = g_node_new(NULL); for (i = 0; fio_options[i].name; i++) { struct fio_option *o = &fio_options[i]; GNode *node, *nparent; /* * Insert node with either the root parent, or an * option parent. */ node = g_node_new(o); nparent = gopt_dep_tree; if (o->parent) { struct fio_option *parent; parent = fio_option_find(o->parent); nparent = g_node_find(gopt_dep_tree, G_IN_ORDER, G_TRAVERSE_ALL, parent); if (!nparent) { log_err("fio: did not find parent %s for opt %s\n", o->name, o->parent); nparent = gopt_dep_tree; } } g_node_insert(nparent, -1, node); } } void gopt_exit(void) { g_node_destroy(gopt_dep_tree); gopt_dep_tree = NULL; } fio-2.1.3/goptions.h000066400000000000000000000002541222032232000142720ustar00rootroot00000000000000#ifndef GFIO_OPTIONS_H #define GFIO_OPTIONS_H void gopt_get_options_window(GtkWidget *window, struct gfio_client *gc); void gopt_init(void); void gopt_exit(void); #endif fio-2.1.3/graph.c000066400000000000000000000563711222032232000135370ustar00rootroot00000000000000/* * gfio - gui front end for fio - the flexible io tester * * Copyright (C) 2012 Stephen M. Cameron * * The license below covers all files distributed with fio unless otherwise * noted in the file itself. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include "tickmarks.h" #include "graph.h" #include "flist.h" #include "lib/prio_tree.h" #include "cairo_text_helpers.h" /* * Allowable difference to show tooltip */ #define TOOLTIP_DELTA 0.08 struct xyvalue { double x, y; }; enum { GV_F_ON_PRIO = 1, GV_F_PRIO_SKIP = 2, }; struct graph_value { struct flist_head list; struct prio_tree_node node; struct flist_head alias; unsigned int flags; char *tooltip; void *value; }; struct graph_label { struct flist_head list; char *label; struct flist_head value_list; struct prio_tree_root prio_tree; double r, g, b; int hide; int value_count; struct graph *parent; }; struct tick_value { unsigned int offset; double value; }; struct graph { char *title; char *xtitle; char *ytitle; unsigned int xdim, ydim; double xoffset, yoffset; struct flist_head label_list; int per_label_limit; const char *font; graph_axis_unit_change_callback x_axis_unit_change_callback; graph_axis_unit_change_callback y_axis_unit_change_callback; unsigned int base_offset; unsigned int dont_graph_all_zeroes; double left_extra; double right_extra; double top_extra; double bottom_extra; double xtick_zero; double xtick_delta; double xtick_zero_val; double xtick_one_val; double ytick_zero; double ytick_delta; double ytick_zero_val; double ytick_one_val; }; void graph_set_size(struct graph *g, unsigned int xdim, unsigned int ydim) { g->xdim = xdim; g->ydim = ydim; } void graph_set_position(struct graph *g, double xoffset, double yoffset) { g->xoffset = xoffset; g->yoffset = yoffset; } struct graph *graph_new(unsigned int xdim, unsigned int ydim, const char *font) { struct graph *g; g = calloc(1, sizeof(*g)); INIT_FLIST_HEAD(&g->label_list); graph_set_size(g, xdim, ydim); g->per_label_limit = -1; g->font = font; if (!g->font) g->font = GRAPH_DEFAULT_FONT; return g; } void graph_set_font(struct graph *g, const char *font) { g->font = font; } void graph_x_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f) { g->x_axis_unit_change_callback = f; } void graph_y_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f) { g->y_axis_unit_change_callback = f; } static int count_labels(struct graph *g) { struct flist_head *entry; int count = 0; flist_for_each(entry, &g->label_list) count++; return count; } static int count_values(struct graph_label *l) { struct flist_head *entry; int count = 0; flist_for_each(entry, &l->value_list) count++; return count; } typedef double (*double_comparator)(double a, double b); static double mindouble(double a, double b) { return a < b ? a : b; } static double maxdouble(double a, double b) { return a < b ? b : a; } static double find_double_values(struct graph_label *l, double_comparator cmp) { struct flist_head *entry; double answer = 0.0, tmp; int first = 1; if (flist_empty(&l->value_list)) return 0.0; flist_for_each(entry, &l->value_list) { struct graph_value *i; i = flist_entry(entry, struct graph_value, list); tmp = *(double *) i->value; if (first) { answer = tmp; first = 0; } else { answer = cmp(answer, tmp); } } return answer; } static double find_double_data(struct graph *g, double_comparator cmp) { struct flist_head *entry; struct graph_label *i; int first = 1; double answer, tmp; if (flist_empty(&g->label_list)) return 0.0; flist_for_each(entry, &g->label_list) { i = flist_entry(entry, struct graph_label, list); tmp = find_double_values(i, cmp); if (first) { answer = tmp; first = 0; } else { answer = cmp(tmp, answer); } } return answer; } static double find_min_data(struct graph *g) { return find_double_data(g, mindouble); } static double find_max_data(struct graph *g) { return find_double_data(g, maxdouble); } static void draw_bars(struct graph *bg, cairo_t *cr, struct graph_label *lb, double label_offset, double bar_width, double mindata, double maxdata) { struct flist_head *entry; double x1, y1, x2, y2; int bar_num = 0; double domain, range, v; domain = (maxdata - mindata); range = (double) bg->ydim * 0.80; /* FIXME */ cairo_stroke(cr); flist_for_each(entry, &lb->value_list) { struct graph_value *i; i = flist_entry(entry, struct graph_value, list); x1 = label_offset + (double) bar_num * bar_width + (bar_width * 0.05); x2 = x1 + bar_width * 0.90; y2 = bg->ydim * 0.90; v = *(double *) i->value; y1 = y2 - (((v - mindata) / domain) * range); cairo_move_to(cr, x1, y1); cairo_line_to(cr, x1, y2); cairo_line_to(cr, x2, y2); cairo_line_to(cr, x2, y1); cairo_close_path(cr); cairo_fill(cr); cairo_stroke(cr); bar_num++; } } static void graph_draw_common(struct graph *g, cairo_t *cr, double *x1, double *y1, double *x2, double *y2) { const double shade_col[3][3] = { { 0.55, 0.54, 0.54 }, { 0.80, 0.78, 0.78 }, { 0.93, 0.91, 0.91 } }; int i; *x1 = 0.10 * g->xdim; *x2 = 0.95 * g->xdim; *y1 = 0.10 * g->ydim; *y2 = 0.90 * g->ydim; /* * Add shade */ cairo_set_line_width(cr, 1.0); for (i = 0; i < 3; i++) { float offset = i + 1.0; cairo_set_source_rgb(cr, shade_col[i][0], shade_col[i][1], shade_col[i][2]); cairo_move_to(cr, offset + *x1, *y1 - offset); cairo_line_to(cr, *x2 + offset, *y1 - offset); cairo_line_to(cr, *x2 + offset, *y2 - offset); cairo_stroke(cr); } cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width(cr, 1.2); cairo_move_to(cr, *x1, *y1); cairo_line_to(cr, *x1, *y2); cairo_line_to(cr, *x2, *y2); cairo_line_to(cr, *x2, *y1); cairo_line_to(cr, *x1, *y1); cairo_stroke(cr); draw_centered_text(cr, g->font, g->xdim / 2, g->ydim / 20, 20.0, g->title); draw_centered_text(cr, g->font, g->xdim / 2, g->ydim * 0.97, 14.0, g->xtitle); draw_vertical_centered_text(cr, g->font, g->xdim * 0.02, g->ydim / 2, 14.0, g->ytitle); cairo_stroke(cr); } static void graph_draw_x_ticks(struct graph *g, cairo_t *cr, double x1, double y1, double x2, double y2, double minx, double maxx, int nticks, int add_tm_text) { struct tickmark *tm; double tx; int i, power_of_ten; static double dash[] = { 1.0, 2.0 }; nticks = calc_tickmarks(minx, maxx, nticks, &tm, &power_of_ten, g->x_axis_unit_change_callback == NULL, g->base_offset); if (g->x_axis_unit_change_callback) g->x_axis_unit_change_callback(g, power_of_ten); for (i = 0; i < nticks; i++) { tx = (((tm[i].value) - minx) / (maxx - minx)) * (x2 - x1) + x1; /* * Update tick delta */ if (!i) { g->xtick_zero = tx; g->xtick_zero_val = tm[0].value; } else if (i == 1) { g->xtick_delta = (tm[1].value - tm[0].value) / (tx - g->xtick_zero); g->xtick_one_val = tm[1].value; } /* really tx < yx || tx > x2, but protect against rounding */ if (x1 - tx > 0.01 || tx - x2 > 0.01) continue; /* Draw tick mark */ cairo_set_line_width(cr, 1.0); cairo_move_to(cr, tx, y2); cairo_line_to(cr, tx, y2 + (y2 - y1) * 0.03); cairo_stroke(cr); /* draw grid lines */ cairo_save(cr); cairo_set_dash(cr, dash, 2, 0.66); cairo_set_line_width(cr, 0.33); cairo_move_to(cr, tx, y1); cairo_line_to(cr, tx, y2); cairo_stroke(cr); cairo_restore(cr); if (!add_tm_text) continue; /* draw tickmark label */ draw_centered_text(cr, g->font, tx, y2 * 1.04, 12.0, tm[i].string); cairo_stroke(cr); } } static double graph_draw_y_ticks(struct graph *g, cairo_t *cr, double x1, double y1, double x2, double y2, double miny, double maxy, int nticks, int add_tm_text) { struct tickmark *tm; double ty; int i, power_of_ten; static double dash[] = { 1.0, 2.0 }; nticks = calc_tickmarks(miny, maxy, nticks, &tm, &power_of_ten, g->y_axis_unit_change_callback == NULL, g->base_offset); if (g->y_axis_unit_change_callback) g->y_axis_unit_change_callback(g, power_of_ten); /* * Use highest tickmark as top of graph, not highest value. Otherwise * it's impossible to see what the max value is, if the graph is * fairly flat. */ maxy = tm[nticks - 1].value; for (i = 0; i < nticks; i++) { ty = y2 - (((tm[i].value) - miny) / (maxy - miny)) * (y2 - y1); /* * Update tick delta */ if (!i) { g->ytick_zero = ty; g->ytick_zero_val = tm[0].value; } else if (i == 1) { g->ytick_delta = (tm[1].value - tm[0].value) / (ty - g->ytick_zero); g->ytick_one_val = tm[1].value; } /* really ty < y1 || ty > y2, but protect against rounding */ if (y1 - ty > 0.01 || ty - y2 > 0.01) continue; /* draw tick mark */ cairo_move_to(cr, x1, ty); cairo_line_to(cr, x1 - (x2 - x1) * 0.02, ty); cairo_stroke(cr); /* draw grid lines */ cairo_save(cr); cairo_set_dash(cr, dash, 2, 0.66); cairo_set_line_width(cr, 0.33); cairo_move_to(cr, x1, ty); cairo_line_to(cr, x2, ty); cairo_stroke(cr); cairo_restore(cr); if (!add_tm_text) continue; /* draw tickmark label */ draw_right_justified_text(cr, g->font, x1 - (x2 - x1) * 0.025, ty, 12.0, tm[i].string); cairo_stroke(cr); } /* * Return new max to use */ return maxy; } void bar_graph_draw(struct graph *bg, cairo_t *cr) { double x1, y1, x2, y2; double space_per_label, bar_width; double label_offset, mindata, maxdata; int i, nlabels; struct graph_label *lb; struct flist_head *entry; cairo_save(cr); cairo_translate(cr, bg->xoffset, bg->yoffset); graph_draw_common(bg, cr, &x1, &y1, &x2, &y2); nlabels = count_labels(bg); space_per_label = (x2 - x1) / (double) nlabels; /* * Start bars at 0 unless we have negative values, otherwise we * present a skewed picture comparing label X and X+1. */ mindata = find_min_data(bg); if (mindata > 0) mindata = 0; maxdata = find_max_data(bg); if (fabs(maxdata - mindata) < 1e-20) { draw_centered_text(cr, bg->font, x1 + (x2 - x1) / 2.0, y1 + (y2 - y1) / 2.0, 20.0, "No good data"); return; } maxdata = graph_draw_y_ticks(bg, cr, x1, y1, x2, y2, mindata, maxdata, 10, 1); i = 0; flist_for_each(entry, &bg->label_list) { int nvalues; lb = flist_entry(entry, struct graph_label, list); nvalues = count_values(lb); bar_width = (space_per_label - space_per_label * 0.2) / (double) nvalues; label_offset = bg->xdim * 0.1 + space_per_label * (double) i + space_per_label * 0.1; draw_bars(bg, cr, lb, label_offset, bar_width, mindata, maxdata); // draw_centered_text(cr, label_offset + (bar_width / 2.0 + bar_width * 0.1), bg->ydim * 0.93, draw_centered_text(cr, bg->font, x1 + space_per_label * (i + 0.5), bg->ydim * 0.93, 12.0, lb->label); i++; } cairo_stroke(cr); cairo_restore(cr); } typedef double (*xy_value_extractor)(struct graph_value *v); static double getx(struct graph_value *v) { struct xyvalue *xy = v->value; return xy->x; } static double gety(struct graph_value *v) { struct xyvalue *xy = v->value; return xy->y; } static double find_xy_value(struct graph *g, xy_value_extractor getvalue, double_comparator cmp) { double tmp, answer = 0.0; struct graph_label *i; struct graph_value *j; struct flist_head *jentry, *entry; int first = 1; flist_for_each(entry, &g->label_list) { i = flist_entry(entry, struct graph_label, list); flist_for_each(jentry, &i->value_list) { j = flist_entry(jentry, struct graph_value, list); tmp = getvalue(j); if (first) { first = 0; answer = tmp; } answer = cmp(tmp, answer); } } return answer; } void line_graph_draw(struct graph *g, cairo_t *cr) { double x1, y1, x2, y2; double minx, miny, maxx, maxy, gminx, gminy, gmaxx, gmaxy; double tx, ty, top_extra, bottom_extra, left_extra, right_extra; struct graph_label *i; struct graph_value *j; int good_data = 1, first = 1; struct flist_head *entry, *lentry; cairo_save(cr); cairo_translate(cr, g->xoffset, g->yoffset); graph_draw_common(g, cr, &x1, &y1, &x2, &y2); minx = find_xy_value(g, getx, mindouble); maxx = find_xy_value(g, getx, maxdouble); miny = find_xy_value(g, gety, mindouble); /* * Start graphs at zero, unless we have a value below. Otherwise * it's hard to visually compare the read and write graph, since * the lowest valued one will be the floor of the graph view. */ if (miny > 0) miny = 0; maxy = find_xy_value(g, gety, maxdouble); if (fabs(maxx - minx) < 1e-20 || fabs(maxy - miny) < 1e-20) { good_data = 0; minx = 0.0; miny = 0.0; maxx = 10.0; maxy = 100.0; } top_extra = 0.0; bottom_extra = 0.0; left_extra = 0.0; right_extra = 0.0; if (g->top_extra > 0.001) top_extra = fabs(maxy - miny) * g->top_extra; if (g->bottom_extra > 0.001) bottom_extra = fabs(maxy - miny) * g->bottom_extra; if (g->left_extra > 0.001) left_extra = fabs(maxx - minx) * g->left_extra; if (g->right_extra > 0.001) right_extra = fabs(maxx - minx) * g->right_extra; gminx = minx - left_extra; gmaxx = maxx + right_extra; gminy = miny - bottom_extra; gmaxy = maxy + top_extra; graph_draw_x_ticks(g, cr, x1, y1, x2, y2, gminx, gmaxx, 10, good_data); gmaxy = graph_draw_y_ticks(g, cr, x1, y1, x2, y2, gminy, gmaxy, 10, good_data); if (!good_data) goto skip_data; cairo_set_line_width(cr, 1.5); cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); flist_for_each(lentry, &g->label_list) { i = flist_entry(lentry, struct graph_label, list); first = 1; if (i->hide || i->r < 0) /* invisible data */ continue; cairo_set_source_rgb(cr, i->r, i->g, i->b); flist_for_each(entry, &i->value_list) { j = flist_entry(entry, struct graph_value, list); tx = ((getx(j) - gminx) / (gmaxx - gminx)) * (x2 - x1) + x1; ty = y2 - ((gety(j) - gminy) / (gmaxy - gminy)) * (y2 - y1); if (first) { cairo_move_to(cr, tx, ty); first = 0; } else cairo_line_to(cr, tx, ty); } cairo_stroke(cr); } skip_data: cairo_restore(cr); } static void setstring(char **str, const char *value) { free(*str); *str = strdup(value); } void graph_title(struct graph *bg, const char *title) { setstring(&bg->title, title); } void graph_x_title(struct graph *bg, const char *title) { setstring(&bg->xtitle, title); } void graph_y_title(struct graph *bg, const char *title) { setstring(&bg->ytitle, title); } static struct graph_label *graph_find_label(struct graph *bg, const char *label) { struct flist_head *entry; struct graph_label *i; flist_for_each(entry, &bg->label_list) { i = flist_entry(entry, struct graph_label, list); if (strcmp(label, i->label) == 0) return i; } return NULL; } graph_label_t graph_add_label(struct graph *bg, const char *label) { struct graph_label *i; i = graph_find_label(bg, label); if (i) return i; /* already present. */ i = calloc(1, sizeof(*i)); INIT_FLIST_HEAD(&i->value_list); i->parent = bg; setstring(&i->label, label); flist_add_tail(&i->list, &bg->label_list); INIT_PRIO_TREE_ROOT(&i->prio_tree); return i; } static void __graph_value_drop(struct graph_label *l, struct graph_value *v) { flist_del_init(&v->list); if (v->tooltip) free(v->tooltip); free(v->value); free(v); l->value_count--; } static void graph_value_drop(struct graph_label *l, struct graph_value *v) { if (v->flags & GV_F_PRIO_SKIP) { __graph_value_drop(l, v); return; } /* * Find head, the guy that's on the prio tree */ while (!(v->flags & GV_F_ON_PRIO)) { assert(!flist_empty(&v->alias)); v = flist_entry(v->alias.next, struct graph_value, alias); } prio_tree_remove(&l->prio_tree, &v->node); /* * Free aliases */ while (!flist_empty(&v->alias)) { struct graph_value *a; a = flist_entry(v->alias.next, struct graph_value, alias); flist_del_init(&a->alias); __graph_value_drop(l, a); } __graph_value_drop(l, v); } static void graph_label_add_value(struct graph_label *i, void *value, const char *tooltip) { struct graph *g = i->parent; struct graph_value *x; x = malloc(sizeof(*x)); memset(x, 0, sizeof(*x)); INIT_FLIST_HEAD(&x->alias); INIT_FLIST_HEAD(&x->list); flist_add_tail(&x->list, &i->value_list); i->value_count++; x->value = value; if (tooltip) { double xval = getx(x); double minx = xval - (g->xtick_one_val * TOOLTIP_DELTA); double maxx = xval + (g->xtick_one_val * TOOLTIP_DELTA); struct prio_tree_node *ret; /* * use msec to avoid dropping too much precision when * storing as an integer. */ minx = minx * 1000.0; maxx = maxx * 1000.0; INIT_PRIO_TREE_NODE(&x->node); x->node.start = minx; x->node.last = maxx; x->tooltip = strdup(tooltip); if (x->node.last == x->node.start) { x->node.last += fabs(g->xtick_delta); if (x->node.last == x->node.start) x->node.last++; } /* * If ret != &x->node, we have an alias. Since the values * should be identical, we can drop it */ ret = prio_tree_insert(&i->prio_tree, &x->node); if (ret != &x->node) { struct graph_value *alias; alias = container_of(ret, struct graph_value, node); flist_add_tail(&x->alias, &alias->alias); } else x->flags = GV_F_ON_PRIO; } else x->flags = GV_F_PRIO_SKIP; if (g->per_label_limit != -1 && i->value_count > g->per_label_limit) { int to_drop = 1; /* * If the limit was dynamically reduced, making us more * than 1 entry ahead after adding this one, drop two * entries. This will make us (eventually) reach the * specified limit. */ if (i->value_count - g->per_label_limit >= 2) to_drop = 2; while (to_drop-- && !flist_empty(&i->value_list)) { x = flist_entry(i->value_list.next, struct graph_value, list); graph_value_drop(i, x); /* * If we have aliases, we could drop > 1 above. */ if (i->value_count <= g->per_label_limit) break; } } } int graph_add_data(struct graph *bg, graph_label_t label, const double value) { struct graph_label *i = label; double *d; d = malloc(sizeof(*d)); *d = value; graph_label_add_value(i, d, NULL); return 0; } static int graph_nonzero_y(struct graph_label *l) { struct flist_head *entry; flist_for_each(entry, &l->value_list) { struct graph_value *v; v = flist_entry(entry, struct graph_value, list); if (gety(v) != 0.0) return 1; } return 0; } int graph_add_xy_data(struct graph *bg, graph_label_t label, const double x, const double y, const char *tooltip) { struct graph_label *i = label; struct xyvalue *xy; if (bg->dont_graph_all_zeroes && y == 0.0 && !graph_nonzero_y(i)) i->hide = 1; else i->hide = 0; xy = malloc(sizeof(*xy)); xy->x = x; xy->y = y; graph_label_add_value(i, xy, tooltip); return 0; } static void graph_free_values(struct graph_label *l) { struct graph_value *i; while (!flist_empty(&l->value_list)) { i = flist_entry(l->value_list.next, struct graph_value, list); graph_value_drop(l, i); } } static void graph_free_labels(struct graph *g) { struct graph_label *i; while (!flist_empty(&g->label_list)) { i = flist_entry(g->label_list.next, struct graph_label, list); flist_del(&i->list); graph_free_values(i); free(i); } } void graph_clear_values(struct graph *g) { struct flist_head *node; struct graph_label *i; flist_for_each(node, &g->label_list) { i = flist_entry(node, struct graph_label, list); graph_free_values(i); } } void graph_set_color(struct graph *gr, graph_label_t label, double red, double green, double blue) { struct graph_label *i = label; double r, g, b; if (red < 0.0) { /* invisible color */ r = -1.0; g = -1.0; b = -1.0; } else { r = fabs(red); g = fabs(green); b = fabs(blue); if (r > 1.0) r = 1.0; if (g > 1.0) g = 1.0; if (b > 1.0) b = 1.0; } i->r = r; i->g = g; i->b = b; } void graph_free(struct graph *bg) { free(bg->title); free(bg->xtitle); free(bg->ytitle); graph_free_labels(bg); } /* For each line in the line graph, up to per_label_limit segments may * be added. After that, adding more data to the end of the line * causes data to drop off of the front of the line. */ void line_graph_set_data_count_limit(struct graph *g, int per_label_limit) { g->per_label_limit = per_label_limit; } void graph_add_extra_space(struct graph *g, double left_percent, double right_percent, double top_percent, double bottom_percent) { g->left_extra = left_percent; g->right_extra = right_percent; g->top_extra = top_percent; g->bottom_extra = bottom_percent; } /* * Normally values are logged in a base unit of 0, but for other purposes * it makes more sense to log in higher unit. For instance for bandwidth * purposes, you may want to log in KB/sec (or MB/sec) rather than bytes/sec. */ void graph_set_base_offset(struct graph *g, unsigned int base_offset) { g->base_offset = base_offset; } int graph_has_tooltips(struct graph *g) { struct flist_head *entry; struct graph_label *i; flist_for_each(entry, &g->label_list) { i = flist_entry(entry, struct graph_label, list); if (!prio_tree_empty(&i->prio_tree)) return 1; } return 0; } int graph_contains_xy(struct graph *g, int x, int y) { int first_x = g->xoffset; int last_x = g->xoffset + g->xdim; int first_y = g->yoffset; int last_y = g->yoffset + g->ydim; return (x >= first_x && x <= last_x) && (y >= first_y && y <= last_y); } const char *graph_find_tooltip(struct graph *g, int ix, int iy) { double x = ix, y = iy; struct prio_tree_iter iter; struct prio_tree_node *n; struct graph_value *best = NULL; struct flist_head *entry; double best_delta; double maxy, miny; x -= g->xoffset; y -= g->yoffset; x = g->xtick_zero_val + ((x - g->xtick_zero) * g->xtick_delta); y = g->ytick_zero_val + ((y - g->ytick_zero) * g->ytick_delta); x = x * 1000.0; maxy = y + (g->ytick_one_val * TOOLTIP_DELTA); miny = y - (g->ytick_one_val * TOOLTIP_DELTA); best_delta = UINT_MAX; flist_for_each(entry, &g->label_list) { struct graph_label *i; i = flist_entry(entry, struct graph_label, list); if (i->hide) continue; INIT_PRIO_TREE_ITER(&iter); prio_tree_iter_init(&iter, &i->prio_tree, x, x); n = prio_tree_next(&iter); if (!n) continue; do { struct graph_value *v, *rootv; double yval, ydiff; v = container_of(n, struct graph_value, node); rootv = v; do { yval = gety(v); ydiff = fabs(yval - y); /* * zero delta, or within or match critera, break */ if (ydiff < best_delta) { best_delta = ydiff; if (!best_delta || (yval >= miny && yval <= maxy)) { best = v; break; } } if (!flist_empty(&v->alias)) v = flist_entry(v->alias.next, struct graph_value, alias); } while (v != rootv); } while ((n = prio_tree_next(&iter)) != NULL); /* * If we got matches in one label, don't check others. */ if (best) break; } if (best) return best->tooltip; return NULL; } void graph_set_graph_all_zeroes(struct graph *g, unsigned int set) { g->dont_graph_all_zeroes = !set; } fio-2.1.3/graph.h000066400000000000000000000107251222032232000135350ustar00rootroot00000000000000#ifndef GRAPH_H #define GRAPH_H struct graph; struct graph_label; typedef struct graph_label * graph_label_t; #define GRAPH_DEFAULT_FONT "Sans 12" struct graph *graph_new(unsigned int xdim, unsigned int ydim, const char *font); /* graph_new() Returns a new graph structure of the given dimensions and font */ void graph_set_size(struct graph *g, unsigned int xdim, unsigned int ydim); /* graph_set_size() Changes the size of a graph to the given dimensions. */ void graph_set_position(struct graph *g, double xoffset, double yoffset); /* graph_set_position() sets the x- and y-offset to translate the graph */ void bar_graph_draw(struct graph *g, cairo_t *cr); /* bar_graph_draw() draws the given graph as a bar graph */ void line_graph_draw(struct graph *g, cairo_t *cr); /* line_graph_draw draws the given graph as a line graph */ void line_graph_set_data_count_limit(struct graph *g, int per_label_limit); /* line_graph_set_data_count_limit() limits the amount of data which can * be added to a line graph. Once the limit is reached, the oldest data * is discarded as new data is added */ void graph_set_font(struct graph *g, const char *font); void graph_title(struct graph *g, const char *title); /* graph_title() sets the main title of the graph to the given string */ void graph_x_title(struct graph *g, const char *title); /* graph_x_title() sets the title of the x axis to the given string */ void graph_y_title(struct graph *g, const char *title); /* graph_y_title() sets the title of the y axis to the given string */ graph_label_t graph_add_label(struct graph *g, const char *label); /* graph_add_label() adds a new "stream" of data to be graphed. * For line charts, each label is a separate line on the graph. * For bar charts, each label is a grouping of columns on the x-axis * For example: * * | * | ** * | * xxxxxxxx | ** * | *** x | ** ** * | *x **** | ** ** ** * | xxxx* ***** | ** xx ** xx ** * | x ** | ** xx ** xx ** xx * | x | ** xx ** xx ** xx * ----------------------- ------------------------- * A B C * * For a line graph, the 'x's For a bar graph, * would be on one "label", and 'A', 'B', and 'C' * the '*'s would be on another are the labels. * label. */ int graph_add_data(struct graph *g, graph_label_t label, const double value); /* graph_add_data() is used to add data to the labels of a bar graph */ int graph_add_xy_data(struct graph *g, graph_label_t label, const double x, const double y, const char *tooltip); /* graph_add_xy_data is used to add data to the labels of a line graph */ void graph_set_color(struct graph *g, graph_label_t label, double red, double green, double blue); #define INVISIBLE_COLOR (-1.0) /* graph_set_color is used to set the color used to plot the data in * a line graph. INVISIBLE_COLOR can be used to plot the data invisibly. * Invisible data will have the same effect on the scaling of the axes * as visible data. */ void graph_free(struct graph *bg); /* free a graph allocated by graph_new() */ typedef void (*graph_axis_unit_change_callback)(struct graph *g, int power_of_ten); void graph_x_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f); void graph_y_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f); /* The labels used on the x and y axes may be shortened. You can register for callbacks * so that you can know how the labels are shorted, typically used to adjust the axis * titles to display the proper units. The power_of_ten parameter indicates what power * of ten the labels have been divided by (9, 6, 3, or 0, corresponding to billions, * millions, thousands and ones. */ void graph_add_extra_space(struct graph *g, double left_percent, double right_percent, double top_percent, double bottom_percent); /* graph_add_extra_space() adds extra space to edges of the the graph * so that the data doesn't go to the very edges. */ extern int graph_has_tooltips(struct graph *g); extern const char *graph_find_tooltip(struct graph *g, int x, int y); extern int graph_contains_xy(struct graph *p, int x, int y); extern void graph_set_base_offset(struct graph *g, unsigned int base_offset); extern void graph_set_graph_all_zeroes(struct graph *g, unsigned int set); extern void graph_clear_values(struct graph *g); #endif fio-2.1.3/hash.h000066400000000000000000000071571222032232000133640ustar00rootroot00000000000000#ifndef _LINUX_HASH_H #define _LINUX_HASH_H #include #include "arch/arch.h" /* Fast hashing routine for a long. (C) 2002 William Lee Irwin III, IBM */ /* * Knuth recommends primes in approximately golden ratio to the maximum * integer representable by a machine word for multiplicative hashing. * Chuck Lever verified the effectiveness of this technique: * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf * * These primes are chosen to be bit-sparse, that is operations on * them can use shifts and additions instead of multiplications for * machines where multiplications are slow. */ #if BITS_PER_LONG == 32 /* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */ #define GOLDEN_RATIO_PRIME 0x9e370001UL #elif BITS_PER_LONG == 64 /* 2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1 */ #define GOLDEN_RATIO_PRIME 0x9e37fffffffc0001UL #else #error Define GOLDEN_RATIO_PRIME for your wordsize. #endif #define GR_PRIME_64 0x9e37fffffffc0001ULL static inline unsigned long __hash_long(unsigned long val) { unsigned long hash = val; #if BITS_PER_LONG == 64 /* Sigh, gcc can't optimise this alone like it does for 32 bits. */ unsigned long n = hash; n <<= 18; hash -= n; n <<= 33; hash -= n; n <<= 3; hash += n; n <<= 3; hash -= n; n <<= 4; hash += n; n <<= 2; hash += n; #else /* On some cpus multiply is faster, on others gcc will do shifts */ hash *= GOLDEN_RATIO_PRIME; #endif return hash; } static inline unsigned long hash_long(unsigned long val, unsigned int bits) { /* High bits are more random, so use them. */ return __hash_long(val) >> (BITS_PER_LONG - bits); } static inline uint64_t __hash_u64(uint64_t val) { return val * GR_PRIME_64; } static inline unsigned long hash_ptr(void *ptr, unsigned int bits) { return hash_long((uintptr_t)ptr, bits); } /* * Bob Jenkins jhash */ #define JHASH_INITVAL GOLDEN_RATIO_PRIME static inline uint32_t rol32(uint32_t word, uint32_t shift) { return (word << shift) | (word >> (32 - shift)); } /* __jhash_mix -- mix 3 32-bit values reversibly. */ #define __jhash_mix(a, b, c) \ { \ a -= c; a ^= rol32(c, 4); c += b; \ b -= a; b ^= rol32(a, 6); a += c; \ c -= b; c ^= rol32(b, 8); b += a; \ a -= c; a ^= rol32(c, 16); c += b; \ b -= a; b ^= rol32(a, 19); a += c; \ c -= b; c ^= rol32(b, 4); b += a; \ } /* __jhash_final - final mixing of 3 32-bit values (a,b,c) into c */ #define __jhash_final(a, b, c) \ { \ c ^= b; c -= rol32(b, 14); \ a ^= c; a -= rol32(c, 11); \ b ^= a; b -= rol32(a, 25); \ c ^= b; c -= rol32(b, 16); \ a ^= c; a -= rol32(c, 4); \ b ^= a; b -= rol32(a, 14); \ c ^= b; c -= rol32(b, 24); \ } static inline uint32_t jhash(const void *key, uint32_t length, uint32_t initval) { const uint8_t *k = key; uint32_t a, b, c; /* Set up the internal state */ a = b = c = JHASH_INITVAL + length + initval; /* All but the last block: affect some 32 bits of (a,b,c) */ while (length > 12) { a += *k; b += *(k + 4); c += *(k + 8); __jhash_mix(a, b, c); length -= 12; k += 12; } /* Last block: affect all 32 bits of (c) */ /* All the case statements fall through */ switch (length) { case 12: c += (uint32_t) k[11] << 24; case 11: c += (uint32_t) k[10] << 16; case 10: c += (uint32_t) k[9] << 8; case 9: c += k[8]; case 8: b += (uint32_t) k[7] << 24; case 7: b += (uint32_t) k[6] << 16; case 6: b += (uint32_t) k[5] << 8; case 5: b += k[4]; case 4: a += (uint32_t) k[3] << 24; case 3: a += (uint32_t) k[2] << 16; case 2: a += (uint32_t) k[1] << 8; case 1: a += k[0]; __jhash_final(a, b, c); case 0: /* Nothing left to add */ break; } return c; } #endif /* _LINUX_HASH_H */ fio-2.1.3/helpers.c000066400000000000000000000013021222032232000140600ustar00rootroot00000000000000#include #include #include #include #include #include #include "compiler/compiler.h" #include "arch/arch.h" #include "os/os.h" #ifndef CONFIG_LINUX_FALLOCATE int fallocate(int fd, int mode, off_t offset, off_t len) { errno = ENOSYS; return -1; } #endif #ifndef CONFIG_POSIX_FALLOCATE int posix_fallocate(int fd, off_t offset, off_t len) { return 0; } #endif #ifndef CONFIG_SYNC_FILE_RANGE int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned int flags) { errno = ENOSYS; return -1; } #endif #ifndef CONFIG_POSIX_FADVISE int posix_fadvise(int fd, off_t offset, off_t len, int advice) { return 0; } #endif fio-2.1.3/helpers.h000066400000000000000000000006661222032232000141010ustar00rootroot00000000000000#ifndef FIO_HELPERS_H #define FIO_HELPERS_H #include "compiler/compiler.h" #include #include extern int fallocate(int fd, int mode, off_t offset, off_t len); extern int posix_fallocate(int fd, off_t offset, off_t len); extern int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned int flags); extern int posix_fadvise(int fd, off_t offset, off_t len, int advice); #endif /* FIO_HELPERS_H_ */ fio-2.1.3/idletime.c000066400000000000000000000260651222032232000142270ustar00rootroot00000000000000#include #include "json.h" #include "idletime.h" static volatile struct idle_prof_common ipc; /* * Get time to complete an unit work on a particular cpu. * The minimum number in CALIBRATE_RUNS runs is returned. */ static double calibrate_unit(unsigned char *data) { unsigned long t, i, j, k; struct timeval tps; double tunit = 0.0; for (i = 0; i < CALIBRATE_RUNS; i++) { fio_gettime(&tps, NULL); /* scale for less variance */ for (j = 0; j < CALIBRATE_SCALE; j++) { /* unit of work */ for (k=0; k < page_size; k++) { data[(k + j) % page_size] = k % 256; /* * we won't see STOP here. this is to match * the same statement in the profiling loop. */ if (ipc.status == IDLE_PROF_STATUS_PROF_STOP) return 0.0; } } t = utime_since_now(&tps); if (!t) continue; /* get the minimum time to complete CALIBRATE_SCALE units */ if ((i == 0) || ((double)t < tunit)) tunit = (double)t; } return tunit / CALIBRATE_SCALE; } static int set_cpu_affinity(struct idle_prof_thread *ipt) { #if defined(FIO_HAVE_CPU_AFFINITY) os_cpu_mask_t cpu_mask; memset(&cpu_mask, 0, sizeof(cpu_mask)); fio_cpu_set(&cpu_mask, ipt->cpu); if (fio_setaffinity(gettid(), cpu_mask)) { log_err("fio: fio_setaffinity failed\n"); return -1; } return 0; #else log_err("fio: fio_setaffinity not supported\n"); return -1; #endif } static void *idle_prof_thread_fn(void *data) { int retval; unsigned long j, k; struct idle_prof_thread *ipt = data; /* wait for all threads are spawned */ pthread_mutex_lock(&ipt->init_lock); /* exit if any other thread failed to start */ if (ipc.status == IDLE_PROF_STATUS_ABORT) return NULL; retval = set_cpu_affinity(ipt); if (retval == -1) { ipt->state = TD_EXITED; pthread_mutex_unlock(&ipt->init_lock); return NULL; } ipt->cali_time = calibrate_unit(ipt->data); /* delay to set IDLE class till now for better calibration accuracy */ #if defined(CONFIG_SCHED_IDLE) if ((retval = fio_set_sched_idle())) log_err("fio: fio_set_sched_idle failed\n"); #else retval = -1; log_err("fio: fio_set_sched_idle not supported\n"); #endif if (retval == -1) { ipt->state = TD_EXITED; pthread_mutex_unlock(&ipt->init_lock); return NULL; } ipt->state = TD_INITIALIZED; /* signal the main thread that calibration is done */ pthread_cond_signal(&ipt->cond); pthread_mutex_unlock(&ipt->init_lock); /* wait for other calibration to finish */ pthread_mutex_lock(&ipt->start_lock); /* exit if other threads failed to initialize */ if (ipc.status == IDLE_PROF_STATUS_ABORT) return NULL; /* exit if we are doing calibration only */ if (ipc.status == IDLE_PROF_STATUS_CALI_STOP) return NULL; fio_gettime(&ipt->tps, NULL); ipt->state = TD_RUNNING; j = 0; while (1) { for (k = 0; k < page_size; k++) { ipt->data[(k + j) % page_size] = k % 256; if (ipc.status == IDLE_PROF_STATUS_PROF_STOP) { fio_gettime(&ipt->tpe, NULL); goto idle_prof_done; } } j++; } idle_prof_done: ipt->loops = j + (double) k / page_size; ipt->state = TD_EXITED; pthread_mutex_unlock(&ipt->start_lock); return NULL; } /* calculate mean and standard deviation to complete an unit of work */ static void calibration_stats(void) { int i; double sum = 0.0, var = 0.0; struct idle_prof_thread *ipt; for (i = 0; i < ipc.nr_cpus; i++) { ipt = &ipc.ipts[i]; sum += ipt->cali_time; } ipc.cali_mean = sum/ipc.nr_cpus; for (i = 0; i < ipc.nr_cpus; i++) { ipt = &ipc.ipts[i]; var += pow(ipt->cali_time-ipc.cali_mean, 2); } ipc.cali_stddev = sqrt(var/(ipc.nr_cpus-1)); } void fio_idle_prof_init(void) { int i, ret; struct timeval tp; struct timespec ts; pthread_attr_t tattr; struct idle_prof_thread *ipt; ipc.nr_cpus = cpus_online(); ipc.status = IDLE_PROF_STATUS_OK; if (ipc.opt == IDLE_PROF_OPT_NONE) return; if ((ret = pthread_attr_init(&tattr))) { log_err("fio: pthread_attr_init %s\n", strerror(ret)); return; } if ((ret = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM))) { log_err("fio: pthread_attr_setscope %s\n", strerror(ret)); return; } ipc.ipts = malloc(ipc.nr_cpus * sizeof(struct idle_prof_thread)); if (!ipc.ipts) { log_err("fio: malloc failed\n"); return; } ipc.buf = malloc(ipc.nr_cpus * page_size); if (!ipc.buf) { log_err("fio: malloc failed\n"); free(ipc.ipts); return; } /* * profiling aborts on any single thread failure since the * result won't be accurate if any cpu is not used. */ for (i = 0; i < ipc.nr_cpus; i++) { ipt = &ipc.ipts[i]; ipt->cpu = i; ipt->state = TD_NOT_CREATED; ipt->data = (unsigned char *)(ipc.buf + page_size * i); if ((ret = pthread_mutex_init(&ipt->init_lock, NULL))) { ipc.status = IDLE_PROF_STATUS_ABORT; log_err("fio: pthread_mutex_init %s\n", strerror(ret)); break; } if ((ret = pthread_mutex_init(&ipt->start_lock, NULL))) { ipc.status = IDLE_PROF_STATUS_ABORT; log_err("fio: pthread_mutex_init %s\n", strerror(ret)); break; } if ((ret = pthread_cond_init(&ipt->cond, NULL))) { ipc.status = IDLE_PROF_STATUS_ABORT; log_err("fio: pthread_cond_init %s\n", strerror(ret)); break; } /* make sure all threads are spawned before they start */ pthread_mutex_lock(&ipt->init_lock); /* make sure all threads finish init before profiling starts */ pthread_mutex_lock(&ipt->start_lock); if ((ret = pthread_create(&ipt->thread, &tattr, idle_prof_thread_fn, ipt))) { ipc.status = IDLE_PROF_STATUS_ABORT; log_err("fio: pthread_create %s\n", strerror(ret)); break; } else ipt->state = TD_CREATED; if ((ret = pthread_detach(ipt->thread))) { /* log error and let the thread spin */ log_err("fio: pthread_detatch %s\n", strerror(ret)); } } /* * let good threads continue so that they can exit * if errors on other threads occurred previously. */ for (i = 0; i < ipc.nr_cpus; i++) { ipt = &ipc.ipts[i]; pthread_mutex_unlock(&ipt->init_lock); } if (ipc.status == IDLE_PROF_STATUS_ABORT) return; /* wait for calibration to finish */ for (i = 0; i < ipc.nr_cpus; i++) { ipt = &ipc.ipts[i]; pthread_mutex_lock(&ipt->init_lock); while ((ipt->state != TD_EXITED) && (ipt->state!=TD_INITIALIZED)) { fio_gettime(&tp, NULL); ts.tv_sec = tp.tv_sec + 1; ts.tv_nsec = tp.tv_usec * 1000; pthread_cond_timedwait(&ipt->cond, &ipt->init_lock, &ts); } pthread_mutex_unlock(&ipt->init_lock); /* * any thread failed to initialize would abort other threads * later after fio_idle_prof_start. */ if (ipt->state == TD_EXITED) ipc.status = IDLE_PROF_STATUS_ABORT; } if (ipc.status != IDLE_PROF_STATUS_ABORT) calibration_stats(); else ipc.cali_mean = ipc.cali_stddev = 0.0; if (ipc.opt == IDLE_PROF_OPT_CALI) ipc.status = IDLE_PROF_STATUS_CALI_STOP; } void fio_idle_prof_start(void) { int i; struct idle_prof_thread *ipt; if (ipc.opt == IDLE_PROF_OPT_NONE) return; /* unlock regardless abort is set or not */ for (i = 0; i < ipc.nr_cpus; i++) { ipt = &ipc.ipts[i]; pthread_mutex_unlock(&ipt->start_lock); } } void fio_idle_prof_stop(void) { int i; uint64_t runt; struct timeval tp; struct timespec ts; struct idle_prof_thread *ipt; if (ipc.opt == IDLE_PROF_OPT_NONE) return; if (ipc.opt == IDLE_PROF_OPT_CALI) return; ipc.status = IDLE_PROF_STATUS_PROF_STOP; /* wait for all threads to exit from profiling */ for (i = 0; i < ipc.nr_cpus; i++) { ipt = &ipc.ipts[i]; pthread_mutex_lock(&ipt->start_lock); while ((ipt->state != TD_EXITED) && (ipt->state!=TD_NOT_CREATED)) { fio_gettime(&tp, NULL); ts.tv_sec = tp.tv_sec + 1; ts.tv_nsec = tp.tv_usec * 1000; /* timed wait in case a signal is not received */ pthread_cond_timedwait(&ipt->cond, &ipt->start_lock, &ts); } pthread_mutex_unlock(&ipt->start_lock); /* calculate idleness */ if (ipc.cali_mean != 0.0) { runt = utime_since(&ipt->tps, &ipt->tpe); ipt->idleness = ipt->loops * ipc.cali_mean / runt; } else ipt->idleness = 0.0; } /* * memory allocations are freed via explicit fio_idle_prof_cleanup * after profiling stats are collected by apps. */ } /* * return system idle percentage when cpu is -1; * return one cpu idle percentage otherwise. */ static double fio_idle_prof_cpu_stat(int cpu) { int i, nr_cpus = ipc.nr_cpus; struct idle_prof_thread *ipt; double p = 0.0; if (ipc.opt == IDLE_PROF_OPT_NONE) return 0.0; if ((cpu >= nr_cpus) || (cpu < -1)) { log_err("fio: idle profiling invalid cpu index\n"); return 0.0; } if (cpu == -1) { for (i = 0; i < nr_cpus; i++) { ipt = &ipc.ipts[i]; p += ipt->idleness; } p /= nr_cpus; } else { ipt = &ipc.ipts[cpu]; p = ipt->idleness; } return p * 100.0; } void fio_idle_prof_cleanup(void) { if (ipc.ipts) { free(ipc.ipts); ipc.ipts = NULL; } if (ipc.buf) { free(ipc.buf); ipc.buf = NULL; } } int fio_idle_prof_parse_opt(const char *args) { ipc.opt = IDLE_PROF_OPT_NONE; /* default */ if (!args) { log_err("fio: empty idle-prof option string\n"); return -1; } #if defined(FIO_HAVE_CPU_AFFINITY) && defined(CONFIG_SCHED_IDLE) if (strcmp("calibrate", args) == 0) { ipc.opt = IDLE_PROF_OPT_CALI; fio_idle_prof_init(); fio_idle_prof_start(); fio_idle_prof_stop(); show_idle_prof_stats(FIO_OUTPUT_NORMAL, NULL); return 1; } else if (strcmp("system", args) == 0) { ipc.opt = IDLE_PROF_OPT_SYSTEM; return 0; } else if (strcmp("percpu", args) == 0) { ipc.opt = IDLE_PROF_OPT_PERCPU; return 0; } else { log_err("fio: incorrect idle-prof option: %s\n", args); return -1; } #else log_err("fio: idle-prof not supported on this platform\n"); return -1; #endif } void show_idle_prof_stats(int output, struct json_object *parent) { int i, nr_cpus = ipc.nr_cpus; struct json_object *tmp; char s[MAX_CPU_STR_LEN]; if (output == FIO_OUTPUT_NORMAL) { if (ipc.opt > IDLE_PROF_OPT_CALI) log_info("\nCPU idleness:\n"); else if (ipc.opt == IDLE_PROF_OPT_CALI) log_info("CPU idleness:\n"); if (ipc.opt >= IDLE_PROF_OPT_SYSTEM) log_info(" system: %3.2f%%\n", fio_idle_prof_cpu_stat(-1)); if (ipc.opt == IDLE_PROF_OPT_PERCPU) { log_info(" percpu: %3.2f%%", fio_idle_prof_cpu_stat(0)); for (i = 1; i < nr_cpus; i++) log_info(", %3.2f%%", fio_idle_prof_cpu_stat(i)); log_info("\n"); } if (ipc.opt >= IDLE_PROF_OPT_CALI) { log_info(" unit work: mean=%3.2fus,", ipc.cali_mean); log_info(" stddev=%3.2f\n", ipc.cali_stddev); } /* dynamic mem allocations can now be freed */ if (ipc.opt != IDLE_PROF_OPT_NONE) fio_idle_prof_cleanup(); return; } if ((ipc.opt != IDLE_PROF_OPT_NONE) && (output == FIO_OUTPUT_JSON)) { if (!parent) return; tmp = json_create_object(); if (!tmp) return; json_object_add_value_object(parent, "cpu_idleness", tmp); json_object_add_value_float(tmp, "system", fio_idle_prof_cpu_stat(-1)); if (ipc.opt == IDLE_PROF_OPT_PERCPU) { for (i = 0; i < nr_cpus; i++) { snprintf(s, MAX_CPU_STR_LEN, "cpu-%d", i); json_object_add_value_float(tmp, s, fio_idle_prof_cpu_stat(i)); } } json_object_add_value_float(tmp, "unit_mean", ipc.cali_mean); json_object_add_value_float(tmp, "unit_stddev", ipc.cali_stddev); fio_idle_prof_cleanup(); } } fio-2.1.3/idletime.h000066400000000000000000000022411222032232000142220ustar00rootroot00000000000000#ifndef FIO_IDLETIME_H #define FIO_IDLETIME_H #include "fio.h" #define CALIBRATE_RUNS 10 #define CALIBRATE_SCALE 1000 #define MAX_CPU_STR_LEN 32 enum { IDLE_PROF_OPT_NONE, IDLE_PROF_OPT_CALI, /* calibration only */ IDLE_PROF_OPT_SYSTEM, IDLE_PROF_OPT_PERCPU }; enum { IDLE_PROF_STATUS_OK, IDLE_PROF_STATUS_CALI_STOP, IDLE_PROF_STATUS_PROF_STOP, IDLE_PROF_STATUS_ABORT }; struct idle_prof_thread { pthread_t thread; int cpu; int state; struct timeval tps; struct timeval tpe; double cali_time; /* microseconds to finish a unit wrok */ double loops; double idleness; unsigned char *data; /* bytes to be touched */ pthread_cond_t cond; pthread_mutex_t init_lock; pthread_mutex_t start_lock; }; struct idle_prof_common { struct idle_prof_thread *ipts; int nr_cpus; int status; int opt; double cali_mean; double cali_stddev; void *buf; /* single data allocation for all threads */ }; extern int fio_idle_prof_parse_opt(const char *); extern void fio_idle_prof_init(void); extern void fio_idle_prof_start(void); extern void fio_idle_prof_stop(void); extern void show_idle_prof_stats(int, struct json_object *); #endif fio-2.1.3/init.c000066400000000000000000001270321222032232000133720ustar00rootroot00000000000000/* * This file contains job initialization and setup functions. */ #include #include #include #include #include #include #include #include #include #include #include "fio.h" #ifndef FIO_NO_HAVE_SHM_H #include #endif #include "parse.h" #include "smalloc.h" #include "filehash.h" #include "verify.h" #include "profile.h" #include "server.h" #include "idletime.h" #include "lib/getopt.h" #include "lib/strcasestr.h" const char fio_version_string[] = FIO_VERSION; #define FIO_RANDSEED (0xb1899bedUL) static char **ini_file; static int max_jobs = FIO_MAX_JOBS; static int dump_cmdline; static int def_timeout; static int parse_only; static struct thread_data def_thread; struct thread_data *threads = NULL; int exitall_on_terminate = 0; int output_format = FIO_OUTPUT_NORMAL; int eta_print = FIO_ETA_AUTO; int eta_new_line = 0; FILE *f_out = NULL; FILE *f_err = NULL; char **job_sections = NULL; int nr_job_sections = 0; char *exec_profile = NULL; int warnings_fatal = 0; int terse_version = 3; int is_backend = 0; int nr_clients = 0; int log_syslog = 0; int write_bw_log = 0; int read_only = 0; int status_interval = 0; static int write_lat_log; static int prev_group_jobs; unsigned long fio_debug = 0; unsigned int fio_debug_jobno = -1; unsigned int *fio_debug_jobp = NULL; static char cmd_optstr[256]; static int did_arg; #define FIO_CLIENT_FLAG (1 << 16) /* * Command line options. These will contain the above, plus a few * extra that only pertain to fio itself and not jobs. */ static struct option l_opts[FIO_NR_OPTIONS] = { { .name = (char *) "output", .has_arg = required_argument, .val = 'o' | FIO_CLIENT_FLAG, }, { .name = (char *) "timeout", .has_arg = required_argument, .val = 't' | FIO_CLIENT_FLAG, }, { .name = (char *) "latency-log", .has_arg = required_argument, .val = 'l' | FIO_CLIENT_FLAG, }, { .name = (char *) "bandwidth-log", .has_arg = required_argument, .val = 'b' | FIO_CLIENT_FLAG, }, { .name = (char *) "minimal", .has_arg = optional_argument, .val = 'm' | FIO_CLIENT_FLAG, }, { .name = (char *) "output-format", .has_arg = optional_argument, .val = 'F' | FIO_CLIENT_FLAG, }, { .name = (char *) "version", .has_arg = no_argument, .val = 'v' | FIO_CLIENT_FLAG, }, { .name = (char *) "help", .has_arg = no_argument, .val = 'h' | FIO_CLIENT_FLAG, }, { .name = (char *) "cmdhelp", .has_arg = optional_argument, .val = 'c' | FIO_CLIENT_FLAG, }, { .name = (char *) "enghelp", .has_arg = optional_argument, .val = 'i' | FIO_CLIENT_FLAG, }, { .name = (char *) "showcmd", .has_arg = no_argument, .val = 's' | FIO_CLIENT_FLAG, }, { .name = (char *) "readonly", .has_arg = no_argument, .val = 'r' | FIO_CLIENT_FLAG, }, { .name = (char *) "eta", .has_arg = required_argument, .val = 'e' | FIO_CLIENT_FLAG, }, { .name = (char *) "eta-newline", .has_arg = required_argument, .val = 'E' | FIO_CLIENT_FLAG, }, { .name = (char *) "debug", .has_arg = required_argument, .val = 'd' | FIO_CLIENT_FLAG, }, { .name = (char *) "parse-only", .has_arg = no_argument, .val = 'P' | FIO_CLIENT_FLAG, }, { .name = (char *) "section", .has_arg = required_argument, .val = 'x' | FIO_CLIENT_FLAG, }, { .name = (char *) "alloc-size", .has_arg = required_argument, .val = 'a' | FIO_CLIENT_FLAG, }, { .name = (char *) "profile", .has_arg = required_argument, .val = 'p' | FIO_CLIENT_FLAG, }, { .name = (char *) "warnings-fatal", .has_arg = no_argument, .val = 'w' | FIO_CLIENT_FLAG, }, { .name = (char *) "max-jobs", .has_arg = required_argument, .val = 'j' | FIO_CLIENT_FLAG, }, { .name = (char *) "terse-version", .has_arg = required_argument, .val = 'V' | FIO_CLIENT_FLAG, }, { .name = (char *) "server", .has_arg = optional_argument, .val = 'S', }, { .name = (char *) "daemonize", .has_arg = required_argument, .val = 'D', }, { .name = (char *) "client", .has_arg = required_argument, .val = 'C', }, { .name = (char *) "cpuclock-test", .has_arg = no_argument, .val = 'T', }, { .name = (char *) "idle-prof", .has_arg = required_argument, .val = 'I', }, { .name = (char *) "status-interval", .has_arg = required_argument, .val = 'L', }, { .name = NULL, }, }; void free_threads_shm(void) { struct shmid_ds sbuf; if (threads) { void *tp = threads; threads = NULL; shmdt(tp); shmctl(shm_id, IPC_RMID, &sbuf); shm_id = -1; } } void free_shm(void) { if (threads) { file_hash_exit(); flow_exit(); fio_debug_jobp = NULL; free_threads_shm(); } scleanup(); } /* * The thread area is shared between the main process and the job * threads/processes. So setup a shared memory segment that will hold * all the job info. We use the end of the region for keeping track of * open files across jobs, for file sharing. */ static int setup_thread_area(void) { void *hash; if (threads) return 0; /* * 1024 is too much on some machines, scale max_jobs if * we get a failure that looks like too large a shm segment */ do { size_t size = max_jobs * sizeof(struct thread_data); size += file_hash_size; size += sizeof(unsigned int); shm_id = shmget(0, size, IPC_CREAT | 0600); if (shm_id != -1) break; if (errno != EINVAL && errno != ENOMEM && errno != ENOSPC) { perror("shmget"); break; } max_jobs >>= 1; } while (max_jobs); if (shm_id == -1) return 1; threads = shmat(shm_id, NULL, 0); if (threads == (void *) -1) { perror("shmat"); return 1; } memset(threads, 0, max_jobs * sizeof(struct thread_data)); hash = (void *) threads + max_jobs * sizeof(struct thread_data); fio_debug_jobp = (void *) hash + file_hash_size; *fio_debug_jobp = -1; file_hash_init(hash); flow_init(); return 0; } /* * Return a free job structure. */ static struct thread_data *get_new_job(int global, struct thread_data *parent, int preserve_eo) { struct thread_data *td; if (global) return &def_thread; if (setup_thread_area()) { log_err("error: failed to setup shm segment\n"); return NULL; } if (thread_number >= max_jobs) { log_err("error: maximum number of jobs (%d) reached.\n", max_jobs); return NULL; } td = &threads[thread_number++]; *td = *parent; td->io_ops = NULL; if (!preserve_eo) td->eo = NULL; td->o.uid = td->o.gid = -1U; dup_files(td, parent); fio_options_mem_dupe(td); profile_add_hooks(td); td->thread_number = thread_number; if (!parent || !parent->o.group_reporting) stat_number++; return td; } static void put_job(struct thread_data *td) { if (td == &def_thread) return; profile_td_exit(td); flow_exit_job(td); if (td->error) log_info("fio: %s\n", td->verror); fio_options_free(td); if (td->io_ops) free_ioengine(td); memset(&threads[td->thread_number - 1], 0, sizeof(*td)); thread_number--; } static int __setup_rate(struct thread_data *td, enum fio_ddir ddir) { unsigned int bs = td->o.min_bs[ddir]; assert(ddir_rw(ddir)); if (td->o.rate[ddir]) td->rate_bps[ddir] = td->o.rate[ddir]; else td->rate_bps[ddir] = td->o.rate_iops[ddir] * bs; if (!td->rate_bps[ddir]) { log_err("rate lower than supported\n"); return -1; } td->rate_pending_usleep[ddir] = 0; return 0; } static int setup_rate(struct thread_data *td) { int ret = 0; if (td->o.rate[DDIR_READ] || td->o.rate_iops[DDIR_READ]) ret = __setup_rate(td, DDIR_READ); if (td->o.rate[DDIR_WRITE] || td->o.rate_iops[DDIR_WRITE]) ret |= __setup_rate(td, DDIR_WRITE); if (td->o.rate[DDIR_TRIM] || td->o.rate_iops[DDIR_TRIM]) ret |= __setup_rate(td, DDIR_TRIM); return ret; } static int fixed_block_size(struct thread_options *o) { return o->min_bs[DDIR_READ] == o->max_bs[DDIR_READ] && o->min_bs[DDIR_WRITE] == o->max_bs[DDIR_WRITE] && o->min_bs[DDIR_TRIM] == o->max_bs[DDIR_TRIM] && o->min_bs[DDIR_READ] == o->min_bs[DDIR_WRITE] && o->min_bs[DDIR_READ] == o->min_bs[DDIR_TRIM]; } /* * Lazy way of fixing up options that depend on each other. We could also * define option callback handlers, but this is easier. */ static int fixup_options(struct thread_data *td) { struct thread_options *o = &td->o; int ret = 0; #ifndef FIO_HAVE_PSHARED_MUTEX if (!o->use_thread) { log_info("fio: this platform does not support process shared" " mutexes, forcing use of threads. Use the 'thread'" " option to get rid of this warning.\n"); o->use_thread = 1; ret = warnings_fatal; } #endif if (o->write_iolog_file && o->read_iolog_file) { log_err("fio: read iolog overrides write_iolog\n"); free(o->write_iolog_file); o->write_iolog_file = NULL; ret = warnings_fatal; } /* * only really works with 1 file */ if (o->zone_size && o->open_files > 1) o->zone_size = 0; /* * If zone_range isn't specified, backward compatibility dictates it * should be made equal to zone_size. */ if (o->zone_size && !o->zone_range) o->zone_range = o->zone_size; /* * Reads can do overwrites, we always need to pre-create the file */ if (td_read(td) || td_rw(td)) o->overwrite = 1; if (!o->min_bs[DDIR_READ]) o->min_bs[DDIR_READ] = o->bs[DDIR_READ]; if (!o->max_bs[DDIR_READ]) o->max_bs[DDIR_READ] = o->bs[DDIR_READ]; if (!o->min_bs[DDIR_WRITE]) o->min_bs[DDIR_WRITE] = o->bs[DDIR_WRITE]; if (!o->max_bs[DDIR_WRITE]) o->max_bs[DDIR_WRITE] = o->bs[DDIR_WRITE]; if (!o->min_bs[DDIR_TRIM]) o->min_bs[DDIR_TRIM] = o->bs[DDIR_TRIM]; if (!o->max_bs[DDIR_TRIM]) o->max_bs[DDIR_TRIM] = o->bs[DDIR_TRIM]; o->rw_min_bs = min(o->min_bs[DDIR_READ], o->min_bs[DDIR_WRITE]); o->rw_min_bs = min(o->min_bs[DDIR_TRIM], o->rw_min_bs); /* * For random IO, allow blockalign offset other than min_bs. */ if (!o->ba[DDIR_READ] || !td_random(td)) o->ba[DDIR_READ] = o->min_bs[DDIR_READ]; if (!o->ba[DDIR_WRITE] || !td_random(td)) o->ba[DDIR_WRITE] = o->min_bs[DDIR_WRITE]; if (!o->ba[DDIR_TRIM] || !td_random(td)) o->ba[DDIR_TRIM] = o->min_bs[DDIR_TRIM]; if ((o->ba[DDIR_READ] != o->min_bs[DDIR_READ] || o->ba[DDIR_WRITE] != o->min_bs[DDIR_WRITE] || o->ba[DDIR_TRIM] != o->min_bs[DDIR_TRIM]) && !o->norandommap) { log_err("fio: Any use of blockalign= turns off randommap\n"); o->norandommap = 1; ret = warnings_fatal; } if (!o->file_size_high) o->file_size_high = o->file_size_low; if (o->norandommap && o->verify != VERIFY_NONE && !fixed_block_size(o)) { log_err("fio: norandommap given for variable block sizes, " "verify disabled\n"); o->verify = VERIFY_NONE; ret = warnings_fatal; } if (o->bs_unaligned && (o->odirect || td->io_ops->flags & FIO_RAWIO)) log_err("fio: bs_unaligned may not work with raw io\n"); /* * thinktime_spin must be less than thinktime */ if (o->thinktime_spin > o->thinktime) o->thinktime_spin = o->thinktime; /* * The low water mark cannot be bigger than the iodepth */ if (o->iodepth_low > o->iodepth || !o->iodepth_low) o->iodepth_low = o->iodepth; /* * If batch number isn't set, default to the same as iodepth */ if (o->iodepth_batch > o->iodepth || !o->iodepth_batch) o->iodepth_batch = o->iodepth; if (o->nr_files > td->files_index) o->nr_files = td->files_index; if (o->open_files > o->nr_files || !o->open_files) o->open_files = o->nr_files; if (((o->rate[DDIR_READ] + o->rate[DDIR_WRITE] + o->rate[DDIR_TRIM]) && (o->rate_iops[DDIR_READ] + o->rate_iops[DDIR_WRITE] + o->rate_iops[DDIR_TRIM])) || ((o->ratemin[DDIR_READ] + o->ratemin[DDIR_WRITE] + o->ratemin[DDIR_TRIM]) && (o->rate_iops_min[DDIR_READ] + o->rate_iops_min[DDIR_WRITE] + o->rate_iops_min[DDIR_TRIM]))) { log_err("fio: rate and rate_iops are mutually exclusive\n"); ret = 1; } if ((o->rate[DDIR_READ] < o->ratemin[DDIR_READ]) || (o->rate[DDIR_WRITE] < o->ratemin[DDIR_WRITE]) || (o->rate[DDIR_TRIM] < o->ratemin[DDIR_TRIM]) || (o->rate_iops[DDIR_READ] < o->rate_iops_min[DDIR_READ]) || (o->rate_iops[DDIR_WRITE] < o->rate_iops_min[DDIR_WRITE]) || (o->rate_iops[DDIR_TRIM] < o->rate_iops_min[DDIR_TRIM])) { log_err("fio: minimum rate exceeds rate\n"); ret = 1; } if (!o->timeout && o->time_based) { log_err("fio: time_based requires a runtime/timeout setting\n"); o->time_based = 0; ret = warnings_fatal; } if (o->fill_device && !o->size) o->size = -1ULL; if (o->verify != VERIFY_NONE) { if (td_write(td) && o->do_verify && o->numjobs > 1) { log_info("Multiple writers may overwrite blocks that " "belong to other jobs. This can cause " "verification failures.\n"); ret = warnings_fatal; } o->refill_buffers = 1; if (o->max_bs[DDIR_WRITE] != o->min_bs[DDIR_WRITE] && !o->verify_interval) o->verify_interval = o->min_bs[DDIR_WRITE]; } if (o->pre_read) { o->invalidate_cache = 0; if (td->io_ops->flags & FIO_PIPEIO) { log_info("fio: cannot pre-read files with an IO engine" " that isn't seekable. Pre-read disabled.\n"); ret = warnings_fatal; } } if (!o->unit_base) { if (td->io_ops->flags & FIO_BIT_BASED) o->unit_base = 1; else o->unit_base = 8; } #ifndef CONFIG_FDATASYNC if (o->fdatasync_blocks) { log_info("fio: this platform does not support fdatasync()" " falling back to using fsync(). Use the 'fsync'" " option instead of 'fdatasync' to get rid of" " this warning\n"); o->fsync_blocks = o->fdatasync_blocks; o->fdatasync_blocks = 0; ret = warnings_fatal; } #endif #ifdef WIN32 /* * Windows doesn't support O_DIRECT or O_SYNC with the _open interface, * so fail if we're passed those flags */ if ((td->io_ops->flags & FIO_SYNCIO) && (td->o.odirect || td->o.sync_io)) { log_err("fio: Windows does not support direct or non-buffered io with" " the synchronous ioengines. Use the 'windowsaio' ioengine" " with 'direct=1' and 'iodepth=1' instead.\n"); ret = 1; } #endif /* * For fully compressible data, just zero them at init time. * It's faster than repeatedly filling it. */ if (td->o.compress_percentage == 100) { td->o.zero_buffers = 1; td->o.compress_percentage = 0; } /* * Using a non-uniform random distribution excludes usage of * a random map */ if (td->o.random_distribution != FIO_RAND_DIST_RANDOM) td->o.norandommap = 1; /* * If size is set but less than the min block size, complain */ if (o->size && o->size < td_min_bs(td)) { log_err("fio: size too small, must be larger than the IO size: %llu\n", (unsigned long long) o->size); ret = 1; } return ret; } /* * This function leaks the buffer */ char *fio_uint_to_kmg(unsigned int val) { char *buf = malloc(32); char post[] = { 0, 'K', 'M', 'G', 'P', 'E', 0 }; char *p = post; do { if (val & 1023) break; val >>= 10; p++; } while (*p); snprintf(buf, 32, "%u%c", val, *p); return buf; } /* External engines are specified by "external:name.o") */ static const char *get_engine_name(const char *str) { char *p = strstr(str, ":"); if (!p) return str; p++; strip_blank_front(&p); strip_blank_end(p); return p; } static int exists_and_not_file(const char *filename) { struct stat sb; if (lstat(filename, &sb) == -1) return 0; /* \\.\ is the device namespace in Windows, where every file * is a device node */ if (S_ISREG(sb.st_mode) && strncmp(filename, "\\\\.\\", 4) != 0) return 0; return 1; } static void td_fill_rand_seeds_os(struct thread_data *td) { os_random_seed(td->rand_seeds[FIO_RAND_BS_OFF], &td->bsrange_state); os_random_seed(td->rand_seeds[FIO_RAND_VER_OFF], &td->verify_state); os_random_seed(td->rand_seeds[FIO_RAND_MIX_OFF], &td->rwmix_state); if (td->o.file_service_type == FIO_FSERVICE_RANDOM) os_random_seed(td->rand_seeds[FIO_RAND_FILE_OFF], &td->next_file_state); os_random_seed(td->rand_seeds[FIO_RAND_FILE_SIZE_OFF], &td->file_size_state); os_random_seed(td->rand_seeds[FIO_RAND_TRIM_OFF], &td->trim_state); if (!td_random(td)) return; if (td->o.rand_repeatable) td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number; os_random_seed(td->rand_seeds[FIO_RAND_BLOCK_OFF], &td->random_state); os_random_seed(td->rand_seeds[FIO_RAND_SEQ_RAND_READ_OFF], &td->seq_rand_state[DDIR_READ]); os_random_seed(td->rand_seeds[FIO_RAND_SEQ_RAND_WRITE_OFF], &td->seq_rand_state[DDIR_WRITE]); os_random_seed(td->rand_seeds[FIO_RAND_SEQ_RAND_TRIM_OFF], &td->seq_rand_state[DDIR_TRIM]); } static void td_fill_rand_seeds_internal(struct thread_data *td) { init_rand_seed(&td->__bsrange_state, td->rand_seeds[FIO_RAND_BS_OFF]); init_rand_seed(&td->__verify_state, td->rand_seeds[FIO_RAND_VER_OFF]); init_rand_seed(&td->__rwmix_state, td->rand_seeds[FIO_RAND_MIX_OFF]); if (td->o.file_service_type == FIO_FSERVICE_RANDOM) init_rand_seed(&td->__next_file_state, td->rand_seeds[FIO_RAND_FILE_OFF]); init_rand_seed(&td->__file_size_state, td->rand_seeds[FIO_RAND_FILE_SIZE_OFF]); init_rand_seed(&td->__trim_state, td->rand_seeds[FIO_RAND_TRIM_OFF]); if (!td_random(td)) return; if (td->o.rand_repeatable) td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number; init_rand_seed(&td->__random_state, td->rand_seeds[FIO_RAND_BLOCK_OFF]); init_rand_seed(&td->__seq_rand_state[DDIR_READ], td->rand_seeds[FIO_RAND_SEQ_RAND_READ_OFF]); init_rand_seed(&td->__seq_rand_state[DDIR_WRITE], td->rand_seeds[FIO_RAND_SEQ_RAND_WRITE_OFF]); init_rand_seed(&td->__seq_rand_state[DDIR_TRIM], td->rand_seeds[FIO_RAND_SEQ_RAND_TRIM_OFF]); } void td_fill_rand_seeds(struct thread_data *td) { if (td->o.use_os_rand) td_fill_rand_seeds_os(td); else td_fill_rand_seeds_internal(td); init_rand_seed(&td->buf_state, td->rand_seeds[FIO_RAND_BUF_OFF]); } /* * Initializes the ioengine configured for a job, if it has not been done so * already. */ int ioengine_load(struct thread_data *td) { const char *engine; /* * Engine has already been loaded. */ if (td->io_ops) return 0; if (!td->o.ioengine) { log_err("fio: internal fault, no IO engine specified\n"); return 1; } engine = get_engine_name(td->o.ioengine); td->io_ops = load_ioengine(td, engine); if (!td->io_ops) { log_err("fio: failed to load engine %s\n", engine); return 1; } if (td->io_ops->option_struct_size && td->io_ops->options) { /* * In cases where td->eo is set, clone it for a child thread. * This requires that the parent thread has the same ioengine, * but that requirement must be enforced by the code which * cloned the thread. */ void *origeo = td->eo; /* * Otherwise use the default thread options. */ if (!origeo && td != &def_thread && def_thread.eo && def_thread.io_ops->options == td->io_ops->options) origeo = def_thread.eo; options_init(td->io_ops->options); td->eo = malloc(td->io_ops->option_struct_size); /* * Use the default thread as an option template if this uses the * same options structure and there are non-default options * used. */ if (origeo) { memcpy(td->eo, origeo, td->io_ops->option_struct_size); options_mem_dupe(td->eo, td->io_ops->options); } else { memset(td->eo, 0, td->io_ops->option_struct_size); fill_default_options(td->eo, td->io_ops->options); } *(struct thread_data **)td->eo = td; } return 0; } static void init_flags(struct thread_data *td) { struct thread_options *o = &td->o; if (o->verify_backlog) td->flags |= TD_F_VER_BACKLOG; if (o->trim_backlog) td->flags |= TD_F_TRIM_BACKLOG; if (o->read_iolog_file) td->flags |= TD_F_READ_IOLOG; if (o->refill_buffers) td->flags |= TD_F_REFILL_BUFFERS; if (o->scramble_buffers) td->flags |= TD_F_SCRAMBLE_BUFFERS; if (o->verify != VERIFY_NONE) td->flags |= TD_F_VER_NONE; } static int setup_random_seeds(struct thread_data *td) { unsigned long seed; unsigned int i; if (!td->o.rand_repeatable) return init_random_state(td, td->rand_seeds, sizeof(td->rand_seeds)); for (seed = 0x89, i = 0; i < 4; i++) seed *= 0x9e370001UL; for (i = 0; i < FIO_RAND_NR_OFFS; i++) { td->rand_seeds[i] = seed; seed *= 0x9e370001UL; } td_fill_rand_seeds(td); return 0; } enum { FPRE_NONE = 0, FPRE_JOBNAME, FPRE_JOBNUM, FPRE_FILENUM }; static struct fpre_keyword { const char *keyword; size_t strlen; int key; } fpre_keywords[] = { { .keyword = "$jobname", .key = FPRE_JOBNAME, }, { .keyword = "$jobnum", .key = FPRE_JOBNUM, }, { .keyword = "$filenum", .key = FPRE_FILENUM, }, { .keyword = NULL, }, }; static char *make_filename(char *buf, struct thread_options *o, const char *jobname, int jobnum, int filenum) { struct fpre_keyword *f; char copy[PATH_MAX]; if (!o->filename_format || !strlen(o->filename_format)) { sprintf(buf, "%s.%d.%d", jobname, jobnum, filenum); return NULL; } for (f = &fpre_keywords[0]; f->keyword; f++) f->strlen = strlen(f->keyword); strcpy(buf, o->filename_format); memset(copy, 0, sizeof(copy)); for (f = &fpre_keywords[0]; f->keyword; f++) { do { size_t pre_len, post_start = 0; char *str, *dst = copy; str = strcasestr(buf, f->keyword); if (!str) break; pre_len = str - buf; if (strlen(str) != f->strlen) post_start = pre_len + f->strlen; if (pre_len) { strncpy(dst, buf, pre_len); dst += pre_len; } switch (f->key) { case FPRE_JOBNAME: dst += sprintf(dst, "%s", jobname); break; case FPRE_JOBNUM: dst += sprintf(dst, "%d", jobnum); break; case FPRE_FILENUM: dst += sprintf(dst, "%d", filenum); break; default: assert(0); break; } if (post_start) strcpy(dst, buf + post_start); strcpy(buf, copy); } while (1); } return buf; } int parse_dryrun(void) { return dump_cmdline || parse_only; } /* * Adds a job to the list of things todo. Sanitizes the various options * to make sure we don't have conflicts, and initializes various * members of td. */ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, int recursed, int client_type) { unsigned int i; char fname[PATH_MAX]; int numjobs, file_alloced; struct thread_options *o = &td->o; /* * the def_thread is just for options, it's not a real job */ if (td == &def_thread) return 0; init_flags(td); /* * if we are just dumping the output command line, don't add the job */ if (parse_dryrun()) { put_job(td); return 0; } td->client_type = client_type; if (profile_td_init(td)) goto err; if (ioengine_load(td)) goto err; if (o->odirect) td->io_ops->flags |= FIO_RAWIO; file_alloced = 0; if (!o->filename && !td->files_index && !o->read_iolog_file) { file_alloced = 1; if (o->nr_files == 1 && exists_and_not_file(jobname)) add_file(td, jobname); else { for (i = 0; i < o->nr_files; i++) add_file(td, make_filename(fname, o, jobname, td->thread_number, i)); } } if (fixup_options(td)) goto err; flow_init_job(td); /* * IO engines only need this for option callbacks, and the address may * change in subprocesses. */ if (td->eo) *(struct thread_data **)td->eo = NULL; if (td->io_ops->flags & FIO_DISKLESSIO) { struct fio_file *f; for_each_file(td, f, i) f->real_file_size = -1ULL; } td->mutex = fio_mutex_init(FIO_MUTEX_LOCKED); td->ts.clat_percentiles = o->clat_percentiles; td->ts.percentile_precision = o->percentile_precision; memcpy(td->ts.percentile_list, o->percentile_list, sizeof(o->percentile_list)); for (i = 0; i < DDIR_RWDIR_CNT; i++) { td->ts.clat_stat[i].min_val = ULONG_MAX; td->ts.slat_stat[i].min_val = ULONG_MAX; td->ts.lat_stat[i].min_val = ULONG_MAX; td->ts.bw_stat[i].min_val = ULONG_MAX; } td->ddir_seq_nr = o->ddir_seq_nr; if ((o->stonewall || o->new_group) && prev_group_jobs) { prev_group_jobs = 0; groupid++; } td->groupid = groupid; prev_group_jobs++; if (setup_random_seeds(td)) { td_verror(td, errno, "init_random_state"); goto err; } if (setup_rate(td)) goto err; if (o->lat_log_file) { setup_log(&td->lat_log, o->log_avg_msec, IO_LOG_TYPE_LAT); setup_log(&td->slat_log, o->log_avg_msec, IO_LOG_TYPE_SLAT); setup_log(&td->clat_log, o->log_avg_msec, IO_LOG_TYPE_CLAT); } if (o->bw_log_file) setup_log(&td->bw_log, o->log_avg_msec, IO_LOG_TYPE_BW); if (o->iops_log_file) setup_log(&td->iops_log, o->log_avg_msec, IO_LOG_TYPE_IOPS); if (!o->name) o->name = strdup(jobname); if (output_format == FIO_OUTPUT_NORMAL) { if (!job_add_num) { if (is_backend && !recursed) fio_server_send_add_job(td); if (!(td->io_ops->flags & FIO_NOIO)) { char *c1, *c2, *c3, *c4; char *c5 = NULL, *c6 = NULL; c1 = fio_uint_to_kmg(o->min_bs[DDIR_READ]); c2 = fio_uint_to_kmg(o->max_bs[DDIR_READ]); c3 = fio_uint_to_kmg(o->min_bs[DDIR_WRITE]); c4 = fio_uint_to_kmg(o->max_bs[DDIR_WRITE]); if (!o->bs_is_seq_rand) { c5 = fio_uint_to_kmg(o->min_bs[DDIR_TRIM]); c6 = fio_uint_to_kmg(o->max_bs[DDIR_TRIM]); } log_info("%s: (g=%d): rw=%s, ", td->o.name, td->groupid, ddir_str(o->td_ddir)); if (o->bs_is_seq_rand) log_info("bs(seq/rand)=%s-%s/%s-%s, ", c1, c2, c3, c4); else log_info("bs=%s-%s/%s-%s/%s-%s, ", c1, c2, c3, c4, c5, c6); log_info("ioengine=%s, iodepth=%u\n", td->io_ops->name, o->iodepth); free(c1); free(c2); free(c3); free(c4); free(c5); free(c6); } } else if (job_add_num == 1) log_info("...\n"); } /* * recurse add identical jobs, clear numjobs and stonewall options * as they don't apply to sub-jobs */ numjobs = o->numjobs; while (--numjobs) { struct thread_data *td_new = get_new_job(0, td, 1); if (!td_new) goto err; td_new->o.numjobs = 1; td_new->o.stonewall = 0; td_new->o.new_group = 0; if (file_alloced) { td_new->o.filename = NULL; td_new->files_index = 0; td_new->files_size = 0; td_new->files = NULL; } job_add_num = numjobs - 1; if (add_job(td_new, jobname, job_add_num, 1, client_type)) goto err; } return 0; err: put_job(td); return -1; } /* * Parse as if 'o' was a command line */ void add_job_opts(const char **o, int client_type) { struct thread_data *td, *td_parent; int i, in_global = 1; char jobname[32]; i = 0; td_parent = td = NULL; while (o[i]) { if (!strncmp(o[i], "name", 4)) { in_global = 0; if (td) add_job(td, jobname, 0, 0, client_type); td = NULL; sprintf(jobname, "%s", o[i] + 5); } if (in_global && !td_parent) td_parent = get_new_job(1, &def_thread, 0); else if (!in_global && !td) { if (!td_parent) td_parent = &def_thread; td = get_new_job(0, td_parent, 0); } if (in_global) fio_options_parse(td_parent, (char **) &o[i], 1); else fio_options_parse(td, (char **) &o[i], 1); i++; } if (td) add_job(td, jobname, 0, 0, client_type); } static int skip_this_section(const char *name) { int i; if (!nr_job_sections) return 0; if (!strncmp(name, "global", 6)) return 0; for (i = 0; i < nr_job_sections; i++) if (!strcmp(job_sections[i], name)) return 0; return 1; } static int is_empty_or_comment(char *line) { unsigned int i; for (i = 0; i < strlen(line); i++) { if (line[i] == ';') return 1; if (line[i] == '#') return 1; if (!isspace((int) line[i]) && !iscntrl((int) line[i])) return 0; } return 1; } /* * This is our [ini] type file parser. */ int parse_jobs_ini(char *file, int is_buf, int stonewall_flag, int type) { unsigned int global; struct thread_data *td; char *string, *name; FILE *f; char *p; int ret = 0, stonewall; int first_sect = 1; int skip_fgets = 0; int inside_skip = 0; char **opts; int i, alloc_opts, num_opts; if (is_buf) f = NULL; else { if (!strcmp(file, "-")) f = stdin; else f = fopen(file, "r"); if (!f) { perror("fopen job file"); return 1; } } string = malloc(4096); /* * it's really 256 + small bit, 280 should suffice */ name = malloc(280); memset(name, 0, 280); alloc_opts = 8; opts = malloc(sizeof(char *) * alloc_opts); num_opts = 0; stonewall = stonewall_flag; do { /* * if skip_fgets is set, we already have loaded a line we * haven't handled. */ if (!skip_fgets) { if (is_buf) p = strsep(&file, "\n"); else p = fgets(string, 4096, f); if (!p) break; } skip_fgets = 0; strip_blank_front(&p); strip_blank_end(p); if (is_empty_or_comment(p)) continue; if (sscanf(p, "[%255[^\n]]", name) != 1) { if (inside_skip) continue; log_err("fio: option <%s> outside of [] job section\n", p); break; } name[strlen(name) - 1] = '\0'; if (skip_this_section(name)) { inside_skip = 1; continue; } else inside_skip = 0; global = !strncmp(name, "global", 6); if (dump_cmdline) { if (first_sect) log_info("fio "); if (!global) log_info("--name=%s ", name); first_sect = 0; } td = get_new_job(global, &def_thread, 0); if (!td) { ret = 1; break; } /* * Seperate multiple job files by a stonewall */ if (!global && stonewall) { td->o.stonewall = stonewall; stonewall = 0; } num_opts = 0; memset(opts, 0, alloc_opts * sizeof(char *)); while (1) { if (is_buf) p = strsep(&file, "\n"); else p = fgets(string, 4096, f); if (!p) break; if (is_empty_or_comment(p)) continue; strip_blank_front(&p); /* * new section, break out and make sure we don't * fgets() a new line at the top. */ if (p[0] == '[') { skip_fgets = 1; break; } strip_blank_end(p); if (num_opts == alloc_opts) { alloc_opts <<= 1; opts = realloc(opts, alloc_opts * sizeof(char *)); } opts[num_opts] = strdup(p); num_opts++; } ret = fio_options_parse(td, opts, num_opts); if (!ret) { if (dump_cmdline) for (i = 0; i < num_opts; i++) log_info("--%s ", opts[i]); ret = add_job(td, name, 0, 0, type); } else { log_err("fio: job %s dropped\n", name); put_job(td); } for (i = 0; i < num_opts; i++) free(opts[i]); num_opts = 0; } while (!ret); if (dump_cmdline) log_info("\n"); i = 0; while (i < nr_job_sections) { free(job_sections[i]); i++; } for (i = 0; i < num_opts; i++) free(opts[i]); free(string); free(name); free(opts); if (!is_buf && f != stdin) fclose(f); return ret; } static int fill_def_thread(void) { memset(&def_thread, 0, sizeof(def_thread)); fio_getaffinity(getpid(), &def_thread.o.cpumask); def_thread.o.timeout = def_timeout; def_thread.o.error_dump = 1; /* * fill default options */ fio_fill_default_options(&def_thread); return 0; } static void usage(const char *name) { printf("%s\n", fio_version_string); printf("%s [options] [job options] \n", name); printf(" --debug=options\tEnable debug logging. May be one/more of:\n" "\t\t\tprocess,file,io,mem,blktrace,verify,random,parse,\n" "\t\t\tdiskutil,job,mutex,profile,time,net\n"); printf(" --parse-only\t\tParse options only, don't start any IO\n"); printf(" --output\t\tWrite output to file\n"); printf(" --runtime\t\tRuntime in seconds\n"); printf(" --latency-log\t\tGenerate per-job latency logs\n"); printf(" --bandwidth-log\tGenerate per-job bandwidth logs\n"); printf(" --minimal\t\tMinimal (terse) output\n"); printf(" --output-format=x\tOutput format (terse,json,normal)\n"); printf(" --terse-version=x\tSet terse version output format to 'x'\n"); printf(" --version\t\tPrint version info and exit\n"); printf(" --help\t\tPrint this page\n"); printf(" --cpuclock-test\tPerform test/validation of CPU clock\n"); printf(" --cmdhelp=cmd\t\tPrint command help, \"all\" for all of" " them\n"); printf(" --enghelp=engine\tPrint ioengine help, or list" " available ioengines\n"); printf(" --enghelp=engine,cmd\tPrint help for an ioengine" " cmd\n"); printf(" --showcmd\t\tTurn a job file into command line options\n"); printf(" --eta=when\t\tWhen ETA estimate should be printed\n"); printf(" \t\tMay be \"always\", \"never\" or \"auto\"\n"); printf(" --eta-newline=time\tForce a new line for every 'time'"); printf(" period passed\n"); printf(" --status-interval=t\tForce full status dump every"); printf(" 't' period passed\n"); printf(" --readonly\t\tTurn on safety read-only checks, preventing" " writes\n"); printf(" --section=name\tOnly run specified section in job file\n"); printf(" --alloc-size=kb\tSet smalloc pool to this size in kb" " (def 1024)\n"); printf(" --warnings-fatal\tFio parser warnings are fatal\n"); printf(" --max-jobs=nr\t\tMaximum number of threads/processes to support\n"); printf(" --server=args\t\tStart a backend fio server\n"); printf(" --daemonize=pidfile\tBackground fio server, write pid to file\n"); printf(" --client=hostname\tTalk to remote backend fio server at hostname\n"); printf(" --idle-prof=option\tReport cpu idleness on a system or percpu basis\n" "\t\t\t(option=system,percpu) or run unit work\n" "\t\t\tcalibration only (option=calibrate)\n"); printf("\nFio was written by Jens Axboe "); printf("\n Jens Axboe \n"); } #ifdef FIO_INC_DEBUG struct debug_level debug_levels[] = { { .name = "process", .help = "Process creation/exit logging", .shift = FD_PROCESS, }, { .name = "file", .help = "File related action logging", .shift = FD_FILE, }, { .name = "io", .help = "IO and IO engine action logging (offsets, queue, completions, etc)", .shift = FD_IO, }, { .name = "mem", .help = "Memory allocation/freeing logging", .shift = FD_MEM, }, { .name = "blktrace", .help = "blktrace action logging", .shift = FD_BLKTRACE, }, { .name = "verify", .help = "IO verification action logging", .shift = FD_VERIFY, }, { .name = "random", .help = "Random generation logging", .shift = FD_RANDOM, }, { .name = "parse", .help = "Parser logging", .shift = FD_PARSE, }, { .name = "diskutil", .help = "Disk utility logging actions", .shift = FD_DISKUTIL, }, { .name = "job", .help = "Logging related to creating/destroying jobs", .shift = FD_JOB, }, { .name = "mutex", .help = "Mutex logging", .shift = FD_MUTEX }, { .name = "profile", .help = "Logging related to profiles", .shift = FD_PROFILE, }, { .name = "time", .help = "Logging related to time keeping functions", .shift = FD_TIME, }, { .name = "net", .help = "Network logging", .shift = FD_NET, }, { .name = NULL, }, }; static int set_debug(const char *string) { struct debug_level *dl; char *p = (char *) string; char *opt; int i; if (!strcmp(string, "?") || !strcmp(string, "help")) { log_info("fio: dumping debug options:"); for (i = 0; debug_levels[i].name; i++) { dl = &debug_levels[i]; log_info("%s,", dl->name); } log_info("all\n"); return 1; } while ((opt = strsep(&p, ",")) != NULL) { int found = 0; if (!strncmp(opt, "all", 3)) { log_info("fio: set all debug options\n"); fio_debug = ~0UL; continue; } for (i = 0; debug_levels[i].name; i++) { dl = &debug_levels[i]; found = !strncmp(opt, dl->name, strlen(dl->name)); if (!found) continue; if (dl->shift == FD_JOB) { opt = strchr(opt, ':'); if (!opt) { log_err("fio: missing job number\n"); break; } opt++; fio_debug_jobno = atoi(opt); log_info("fio: set debug jobno %d\n", fio_debug_jobno); } else { log_info("fio: set debug option %s\n", opt); fio_debug |= (1UL << dl->shift); } break; } if (!found) log_err("fio: debug mask %s not found\n", opt); } return 0; } #else static int set_debug(const char *string) { log_err("fio: debug tracing not included in build\n"); return 1; } #endif static void fio_options_fill_optstring(void) { char *ostr = cmd_optstr; int i, c; c = i = 0; while (l_opts[i].name) { ostr[c++] = l_opts[i].val; if (l_opts[i].has_arg == required_argument) ostr[c++] = ':'; else if (l_opts[i].has_arg == optional_argument) { ostr[c++] = ':'; ostr[c++] = ':'; } i++; } ostr[c] = '\0'; } static int client_flag_set(char c) { int i; i = 0; while (l_opts[i].name) { int val = l_opts[i].val; if (c == (val & 0xff)) return (val & FIO_CLIENT_FLAG); i++; } return 0; } void parse_cmd_client(void *client, char *opt) { fio_client_add_cmd_option(client, opt); } int parse_cmd_line(int argc, char *argv[], int client_type) { struct thread_data *td = NULL; int c, ini_idx = 0, lidx, ret = 0, do_exit = 0, exit_val = 0; char *ostr = cmd_optstr; void *pid_file = NULL; void *cur_client = NULL; int backend = 0; /* * Reset optind handling, since we may call this multiple times * for the backend. */ optind = 1; while ((c = getopt_long_only(argc, argv, ostr, l_opts, &lidx)) != -1) { did_arg = 1; if ((c & FIO_CLIENT_FLAG) || client_flag_set(c)) { parse_cmd_client(cur_client, argv[optind - 1]); c &= ~FIO_CLIENT_FLAG; } switch (c) { case 'a': smalloc_pool_size = atoi(optarg); break; case 't': def_timeout = atoi(optarg); break; case 'l': write_lat_log = 1; break; case 'b': write_bw_log = 1; break; case 'o': f_out = fopen(optarg, "w+"); if (!f_out) { perror("fopen output"); exit(1); } f_err = f_out; break; case 'm': output_format = FIO_OUTPUT_TERSE; break; case 'F': if (!strcmp(optarg, "minimal") || !strcmp(optarg, "terse") || !strcmp(optarg, "csv")) output_format = FIO_OUTPUT_TERSE; else if (!strcmp(optarg, "json")) output_format = FIO_OUTPUT_JSON; else output_format = FIO_OUTPUT_NORMAL; break; case 'h': if (!cur_client) { usage(argv[0]); do_exit++; } break; case 'c': if (!cur_client) { fio_show_option_help(optarg); do_exit++; } break; case 'i': if (!cur_client) { fio_show_ioengine_help(optarg); do_exit++; } break; case 's': dump_cmdline = 1; break; case 'r': read_only = 1; break; case 'v': if (!cur_client) { log_info("%s\n", fio_version_string); do_exit++; } break; case 'V': terse_version = atoi(optarg); if (!(terse_version == 2 || terse_version == 3 || terse_version == 4)) { log_err("fio: bad terse version format\n"); exit_val = 1; do_exit++; } break; case 'e': if (!strcmp("always", optarg)) eta_print = FIO_ETA_ALWAYS; else if (!strcmp("never", optarg)) eta_print = FIO_ETA_NEVER; break; case 'E': { long long t = 0; if (str_to_decimal(optarg, &t, 0, NULL)) { log_err("fio: failed parsing eta time %s\n", optarg); exit_val = 1; do_exit++; } eta_new_line = t; break; } case 'd': if (set_debug(optarg)) do_exit++; break; case 'P': parse_only = 1; break; case 'x': { size_t new_size; if (!strcmp(optarg, "global")) { log_err("fio: can't use global as only " "section\n"); do_exit++; exit_val = 1; break; } new_size = (nr_job_sections + 1) * sizeof(char *); job_sections = realloc(job_sections, new_size); job_sections[nr_job_sections] = strdup(optarg); nr_job_sections++; break; } case 'p': exec_profile = strdup(optarg); break; case FIO_GETOPT_JOB: { const char *opt = l_opts[lidx].name; char *val = optarg; if (!strncmp(opt, "name", 4) && td) { ret = add_job(td, td->o.name ?: "fio", 0, 0, client_type); if (ret) return 0; td = NULL; } if (!td) { int is_section = !strncmp(opt, "name", 4); int global = 0; if (!is_section || !strncmp(val, "global", 6)) global = 1; if (is_section && skip_this_section(val)) continue; td = get_new_job(global, &def_thread, 1); if (!td || ioengine_load(td)) return 0; fio_options_set_ioengine_opts(l_opts, td); } if ((!val || !strlen(val)) && l_opts[lidx].has_arg == required_argument) { log_err("fio: option %s requires an argument\n", opt); ret = 1; } else ret = fio_cmd_option_parse(td, opt, val); if (ret) { if (td) { put_job(td); td = NULL; } do_exit++; } if (!ret && !strcmp(opt, "ioengine")) { free_ioengine(td); if (ioengine_load(td)) return 0; fio_options_set_ioengine_opts(l_opts, td); } break; } case FIO_GETOPT_IOENGINE: { const char *opt = l_opts[lidx].name; char *val = optarg; ret = fio_cmd_ioengine_option_parse(td, opt, val); break; } case 'w': warnings_fatal = 1; break; case 'j': max_jobs = atoi(optarg); if (!max_jobs || max_jobs > REAL_MAX_JOBS) { log_err("fio: invalid max jobs: %d\n", max_jobs); do_exit++; exit_val = 1; } break; case 'S': if (nr_clients) { log_err("fio: can't be both client and server\n"); do_exit++; exit_val = 1; break; } if (optarg) fio_server_set_arg(optarg); is_backend = 1; backend = 1; break; case 'D': pid_file = strdup(optarg); break; case 'I': if ((ret = fio_idle_prof_parse_opt(optarg))) { /* exit on error and calibration only */ do_exit++; if (ret == -1) exit_val = 1; } break; case 'C': if (is_backend) { log_err("fio: can't be both client and server\n"); do_exit++; exit_val = 1; break; } if (fio_client_add(&fio_client_ops, optarg, &cur_client)) { log_err("fio: failed adding client %s\n", optarg); do_exit++; exit_val = 1; break; } /* * If the next argument exists and isn't an option, * assume it's a job file for this client only. */ while (optind < argc) { if (!strncmp(argv[optind], "--", 2) || !strncmp(argv[optind], "-", 1)) break; fio_client_add_ini_file(cur_client, argv[optind]); optind++; } break; case 'T': do_exit++; exit_val = fio_monotonic_clocktest(); break; case 'L': { long long val; if (check_str_time(optarg, &val)) { log_err("fio: failed parsing time %s\n", optarg); do_exit++; exit_val = 1; break; } status_interval = val * 1000; break; } case '?': log_err("%s: unrecognized option '%s'\n", argv[0], argv[optind - 1]); default: do_exit++; exit_val = 1; break; } if (do_exit) break; } if (do_exit && !(is_backend || nr_clients)) exit(exit_val); if (nr_clients && fio_clients_connect()) { do_exit++; exit_val = 1; return -1; } if (is_backend && backend) return fio_start_server(pid_file); if (td) { if (!ret) ret = add_job(td, td->o.name ?: "fio", 0, 0, client_type); } while (!ret && optind < argc) { ini_idx++; ini_file = realloc(ini_file, ini_idx * sizeof(char *)); ini_file[ini_idx - 1] = strdup(argv[optind]); optind++; } return ini_idx; } int fio_init_options(void) { f_out = stdout; f_err = stderr; fio_options_fill_optstring(); fio_options_dup_and_init(l_opts); atexit(free_shm); if (fill_def_thread()) return 1; return 0; } extern int fio_check_options(struct thread_options *); int parse_options(int argc, char *argv[]) { const int type = FIO_CLIENT_TYPE_CLI; int job_files, i; if (fio_init_options()) return 1; if (fio_test_cconv(&def_thread.o)) log_err("fio: failed internal cconv test\n"); job_files = parse_cmd_line(argc, argv, type); if (job_files > 0) { for (i = 0; i < job_files; i++) { if (fill_def_thread()) return 1; if (nr_clients) { if (fio_clients_send_ini(ini_file[i])) return 1; free(ini_file[i]); } else if (!is_backend) { if (parse_jobs_ini(ini_file[i], 0, i, type)) return 1; free(ini_file[i]); } } } else if (nr_clients) { if (fill_def_thread()) return 1; if (fio_clients_send_ini(NULL)) return 1; } free(ini_file); fio_options_free(&def_thread); if (!thread_number) { if (parse_dryrun()) return 0; if (exec_profile) return 0; if (is_backend || nr_clients) return 0; if (did_arg) return 0; log_err("No jobs(s) defined\n\n"); if (!did_arg) { usage(argv[0]); return 1; } return 0; } if (def_thread.o.gtod_offload) { fio_gtod_init(); fio_gtod_offload = 1; fio_gtod_cpu = def_thread.o.gtod_cpu; } if (output_format == FIO_OUTPUT_NORMAL) log_info("%s\n", fio_version_string); return 0; } void options_default_fill(struct thread_options *o) { memcpy(o, &def_thread.o, sizeof(*o)); } fio-2.1.3/io_ddir.h000066400000000000000000000030461222032232000140430ustar00rootroot00000000000000#ifndef FIO_DDIR_H #define FIO_DDIR_H enum fio_ddir { DDIR_READ = 0, DDIR_WRITE = 1, DDIR_TRIM = 2, DDIR_RWDIR_CNT = 3, DDIR_SYNC = 3, DDIR_DATASYNC, DDIR_SYNC_FILE_RANGE, DDIR_WAIT, DDIR_INVAL = -1, }; enum td_ddir { TD_DDIR_READ = 1 << 0, TD_DDIR_WRITE = 1 << 1, TD_DDIR_RAND = 1 << 2, TD_DDIR_TRIM = 1 << 3, TD_DDIR_RW = TD_DDIR_READ | TD_DDIR_WRITE, TD_DDIR_RANDREAD = TD_DDIR_READ | TD_DDIR_RAND, TD_DDIR_RANDWRITE = TD_DDIR_WRITE | TD_DDIR_RAND, TD_DDIR_RANDRW = TD_DDIR_RW | TD_DDIR_RAND, TD_DDIR_RANDTRIM = TD_DDIR_TRIM | TD_DDIR_RAND, }; #define td_read(td) ((td)->o.td_ddir & TD_DDIR_READ) #define td_write(td) ((td)->o.td_ddir & TD_DDIR_WRITE) #define td_trim(td) ((td)->o.td_ddir & TD_DDIR_TRIM) #define td_rw(td) (((td)->o.td_ddir & TD_DDIR_RW) == TD_DDIR_RW) #define td_random(td) ((td)->o.td_ddir & TD_DDIR_RAND) #define file_randommap(td, f) (!(td)->o.norandommap && (f)->io_axmap) static inline int ddir_sync(enum fio_ddir ddir) { return ddir == DDIR_SYNC || ddir == DDIR_DATASYNC || ddir == DDIR_SYNC_FILE_RANGE; } static inline int ddir_rw(enum fio_ddir ddir) { return ddir == DDIR_READ || ddir == DDIR_WRITE || ddir == DDIR_TRIM; } static inline const char *ddir_str(enum td_ddir ddir) { const char *ddir_str[] = { NULL, "read", "write", "rw", NULL, "randread", "randwrite", "randrw", "trim", NULL, NULL, NULL, "randtrim" }; return ddir_str[ddir]; } #define ddir_trim(ddir) ((ddir) == DDIR_TRIM) #define ddir_rw_sum(arr) \ ((arr)[DDIR_READ] + (arr)[DDIR_WRITE] + (arr)[DDIR_TRIM]) #endif fio-2.1.3/io_u.c000066400000000000000000001073441222032232000133660ustar00rootroot00000000000000#include #include #include #include #include #include #include "fio.h" #include "hash.h" #include "verify.h" #include "trim.h" #include "lib/rand.h" #include "lib/axmap.h" struct io_completion_data { int nr; /* input */ int error; /* output */ uint64_t bytes_done[DDIR_RWDIR_CNT]; /* output */ struct timeval time; /* output */ }; /* * The ->io_axmap contains a map of blocks we have or have not done io * to yet. Used to make sure we cover the entire range in a fair fashion. */ static int random_map_free(struct fio_file *f, const uint64_t block) { return !axmap_isset(f->io_axmap, block); } /* * Mark a given offset as used in the map. */ static void mark_random_map(struct thread_data *td, struct io_u *io_u) { unsigned int min_bs = td->o.rw_min_bs; struct fio_file *f = io_u->file; unsigned int nr_blocks; uint64_t block; block = (io_u->offset - f->file_offset) / (uint64_t) min_bs; nr_blocks = (io_u->buflen + min_bs - 1) / min_bs; if (!(io_u->flags & IO_U_F_BUSY_OK)) nr_blocks = axmap_set_nr(f->io_axmap, block, nr_blocks); if ((nr_blocks * min_bs) < io_u->buflen) io_u->buflen = nr_blocks * min_bs; } static uint64_t last_block(struct thread_data *td, struct fio_file *f, enum fio_ddir ddir) { uint64_t max_blocks; uint64_t max_size; assert(ddir_rw(ddir)); /* * Hmm, should we make sure that ->io_size <= ->real_file_size? */ max_size = f->io_size; if (max_size > f->real_file_size) max_size = f->real_file_size; if (td->o.zone_range) max_size = td->o.zone_range; max_blocks = max_size / (uint64_t) td->o.ba[ddir]; if (!max_blocks) return 0; return max_blocks; } struct rand_off { struct flist_head list; uint64_t off; }; static int __get_next_rand_offset(struct thread_data *td, struct fio_file *f, enum fio_ddir ddir, uint64_t *b) { uint64_t r, lastb; lastb = last_block(td, f, ddir); if (!lastb) return 1; if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE) { uint64_t rmax; rmax = td->o.use_os_rand ? OS_RAND_MAX : FRAND_MAX; if (td->o.use_os_rand) { rmax = OS_RAND_MAX; r = os_random_long(&td->random_state); } else { rmax = FRAND_MAX; r = __rand(&td->__random_state); } dprint(FD_RANDOM, "off rand %llu\n", (unsigned long long) r); *b = (lastb - 1) * (r / ((uint64_t) rmax + 1.0)); } else { uint64_t off = 0; if (lfsr_next(&f->lfsr, &off, lastb)) return 1; *b = off; } /* * if we are not maintaining a random map, we are done. */ if (!file_randommap(td, f)) goto ret; /* * calculate map offset and check if it's free */ if (random_map_free(f, *b)) goto ret; dprint(FD_RANDOM, "get_next_rand_offset: offset %llu busy\n", (unsigned long long) *b); *b = axmap_next_free(f->io_axmap, *b); if (*b == (uint64_t) -1ULL) return 1; ret: return 0; } static int __get_next_rand_offset_zipf(struct thread_data *td, struct fio_file *f, enum fio_ddir ddir, uint64_t *b) { *b = zipf_next(&f->zipf); return 0; } static int __get_next_rand_offset_pareto(struct thread_data *td, struct fio_file *f, enum fio_ddir ddir, uint64_t *b) { *b = pareto_next(&f->zipf); return 0; } static int flist_cmp(void *data, struct flist_head *a, struct flist_head *b) { struct rand_off *r1 = flist_entry(a, struct rand_off, list); struct rand_off *r2 = flist_entry(b, struct rand_off, list); return r1->off - r2->off; } static int get_off_from_method(struct thread_data *td, struct fio_file *f, enum fio_ddir ddir, uint64_t *b) { if (td->o.random_distribution == FIO_RAND_DIST_RANDOM) return __get_next_rand_offset(td, f, ddir, b); else if (td->o.random_distribution == FIO_RAND_DIST_ZIPF) return __get_next_rand_offset_zipf(td, f, ddir, b); else if (td->o.random_distribution == FIO_RAND_DIST_PARETO) return __get_next_rand_offset_pareto(td, f, ddir, b); log_err("fio: unknown random distribution: %d\n", td->o.random_distribution); return 1; } /* * Sort the reads for a verify phase in batches of verifysort_nr, if * specified. */ static inline int should_sort_io(struct thread_data *td) { if (!td->o.verifysort_nr || !td->o.do_verify) return 0; if (!td_random(td)) return 0; if (td->runstate != TD_VERIFYING) return 0; if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE) return 0; return 1; } static int should_do_random(struct thread_data *td, enum fio_ddir ddir) { unsigned int v; unsigned long r; if (td->o.perc_rand[ddir] == 100) return 1; if (td->o.use_os_rand) { r = os_random_long(&td->seq_rand_state[ddir]); v = 1 + (int) (100.0 * (r / (OS_RAND_MAX + 1.0))); } else { r = __rand(&td->__seq_rand_state[ddir]); v = 1 + (int) (100.0 * (r / (FRAND_MAX + 1.0))); } return v <= td->o.perc_rand[ddir]; } static int get_next_rand_offset(struct thread_data *td, struct fio_file *f, enum fio_ddir ddir, uint64_t *b) { struct rand_off *r; int i, ret = 1; if (!should_sort_io(td)) return get_off_from_method(td, f, ddir, b); if (!flist_empty(&td->next_rand_list)) { struct rand_off *r; fetch: r = flist_entry(td->next_rand_list.next, struct rand_off, list); flist_del(&r->list); *b = r->off; free(r); return 0; } for (i = 0; i < td->o.verifysort_nr; i++) { r = malloc(sizeof(*r)); ret = get_off_from_method(td, f, ddir, &r->off); if (ret) { free(r); break; } flist_add(&r->list, &td->next_rand_list); } if (ret && !i) return ret; assert(!flist_empty(&td->next_rand_list)); flist_sort(NULL, &td->next_rand_list, flist_cmp); goto fetch; } static int get_next_rand_block(struct thread_data *td, struct fio_file *f, enum fio_ddir ddir, uint64_t *b) { if (!get_next_rand_offset(td, f, ddir, b)) return 0; if (td->o.time_based) { fio_file_reset(td, f); if (!get_next_rand_offset(td, f, ddir, b)) return 0; } dprint(FD_IO, "%s: rand offset failed, last=%llu, size=%llu\n", f->file_name, (unsigned long long) f->last_pos, (unsigned long long) f->real_file_size); return 1; } static int get_next_seq_offset(struct thread_data *td, struct fio_file *f, enum fio_ddir ddir, uint64_t *offset) { assert(ddir_rw(ddir)); if (f->last_pos >= f->io_size + get_start_offset(td) && td->o.time_based) f->last_pos = f->last_pos - f->io_size; if (f->last_pos < f->real_file_size) { uint64_t pos; if (f->last_pos == f->file_offset && td->o.ddir_seq_add < 0) f->last_pos = f->real_file_size; pos = f->last_pos - f->file_offset; if (pos) pos += td->o.ddir_seq_add; *offset = pos; return 0; } return 1; } static int get_next_block(struct thread_data *td, struct io_u *io_u, enum fio_ddir ddir, int rw_seq, unsigned int *is_random) { struct fio_file *f = io_u->file; uint64_t b, offset; int ret; assert(ddir_rw(ddir)); b = offset = -1ULL; if (rw_seq) { if (td_random(td)) { if (should_do_random(td, ddir)) { ret = get_next_rand_block(td, f, ddir, &b); *is_random = 1; } else { *is_random = 0; io_u->flags |= IO_U_F_BUSY_OK; ret = get_next_seq_offset(td, f, ddir, &offset); if (ret) ret = get_next_rand_block(td, f, ddir, &b); } } else { *is_random = 0; ret = get_next_seq_offset(td, f, ddir, &offset); } } else { io_u->flags |= IO_U_F_BUSY_OK; *is_random = 0; if (td->o.rw_seq == RW_SEQ_SEQ) { ret = get_next_seq_offset(td, f, ddir, &offset); if (ret) { ret = get_next_rand_block(td, f, ddir, &b); *is_random = 0; } } else if (td->o.rw_seq == RW_SEQ_IDENT) { if (f->last_start != -1ULL) offset = f->last_start - f->file_offset; else offset = 0; ret = 0; } else { log_err("fio: unknown rw_seq=%d\n", td->o.rw_seq); ret = 1; } } if (!ret) { if (offset != -1ULL) io_u->offset = offset; else if (b != -1ULL) io_u->offset = b * td->o.ba[ddir]; else { log_err("fio: bug in offset generation: offset=%llu, b=%llu\n", (unsigned long long) offset, (unsigned long long) b); ret = 1; } } return ret; } /* * For random io, generate a random new block and see if it's used. Repeat * until we find a free one. For sequential io, just return the end of * the last io issued. */ static int __get_next_offset(struct thread_data *td, struct io_u *io_u, unsigned int *is_random) { struct fio_file *f = io_u->file; enum fio_ddir ddir = io_u->ddir; int rw_seq_hit = 0; assert(ddir_rw(ddir)); if (td->o.ddir_seq_nr && !--td->ddir_seq_nr) { rw_seq_hit = 1; td->ddir_seq_nr = td->o.ddir_seq_nr; } if (get_next_block(td, io_u, ddir, rw_seq_hit, is_random)) return 1; if (io_u->offset >= f->io_size) { dprint(FD_IO, "get_next_offset: offset %llu >= io_size %llu\n", (unsigned long long) io_u->offset, (unsigned long long) f->io_size); return 1; } io_u->offset += f->file_offset; if (io_u->offset >= f->real_file_size) { dprint(FD_IO, "get_next_offset: offset %llu >= size %llu\n", (unsigned long long) io_u->offset, (unsigned long long) f->real_file_size); return 1; } return 0; } static int get_next_offset(struct thread_data *td, struct io_u *io_u, unsigned int *is_random) { if (td->flags & TD_F_PROFILE_OPS) { struct prof_io_ops *ops = &td->prof_io_ops; if (ops->fill_io_u_off) return ops->fill_io_u_off(td, io_u, is_random); } return __get_next_offset(td, io_u, is_random); } static inline int io_u_fits(struct thread_data *td, struct io_u *io_u, unsigned int buflen) { struct fio_file *f = io_u->file; return io_u->offset + buflen <= f->io_size + get_start_offset(td); } static unsigned int __get_next_buflen(struct thread_data *td, struct io_u *io_u, unsigned int is_random) { int ddir = io_u->ddir; unsigned int buflen = 0; unsigned int minbs, maxbs; unsigned long r, rand_max; assert(ddir_rw(io_u->ddir)); if (td->o.bs_is_seq_rand) ddir = is_random ? DDIR_WRITE: DDIR_READ; else ddir = io_u->ddir; minbs = td->o.min_bs[ddir]; maxbs = td->o.max_bs[ddir]; if (minbs == maxbs) return minbs; /* * If we can't satisfy the min block size from here, then fail */ if (!io_u_fits(td, io_u, minbs)) return 0; if (td->o.use_os_rand) rand_max = OS_RAND_MAX; else rand_max = FRAND_MAX; do { if (td->o.use_os_rand) r = os_random_long(&td->bsrange_state); else r = __rand(&td->__bsrange_state); if (!td->o.bssplit_nr[ddir]) { buflen = 1 + (unsigned int) ((double) maxbs * (r / (rand_max + 1.0))); if (buflen < minbs) buflen = minbs; } else { long perc = 0; unsigned int i; for (i = 0; i < td->o.bssplit_nr[ddir]; i++) { struct bssplit *bsp = &td->o.bssplit[ddir][i]; buflen = bsp->bs; perc += bsp->perc; if ((r <= ((rand_max / 100L) * perc)) && io_u_fits(td, io_u, buflen)) break; } } if (td->o.do_verify && td->o.verify != VERIFY_NONE) buflen = (buflen + td->o.verify_interval - 1) & ~(td->o.verify_interval - 1); if (!td->o.bs_unaligned && is_power_of_2(minbs)) buflen = (buflen + minbs - 1) & ~(minbs - 1); } while (!io_u_fits(td, io_u, buflen)); return buflen; } static unsigned int get_next_buflen(struct thread_data *td, struct io_u *io_u, unsigned int is_random) { if (td->flags & TD_F_PROFILE_OPS) { struct prof_io_ops *ops = &td->prof_io_ops; if (ops->fill_io_u_size) return ops->fill_io_u_size(td, io_u, is_random); } return __get_next_buflen(td, io_u, is_random); } static void set_rwmix_bytes(struct thread_data *td) { unsigned int diff; /* * we do time or byte based switch. this is needed because * buffered writes may issue a lot quicker than they complete, * whereas reads do not. */ diff = td->o.rwmix[td->rwmix_ddir ^ 1]; td->rwmix_issues = (td->io_issues[td->rwmix_ddir] * diff) / 100; } static inline enum fio_ddir get_rand_ddir(struct thread_data *td) { unsigned int v; unsigned long r; if (td->o.use_os_rand) { r = os_random_long(&td->rwmix_state); v = 1 + (int) (100.0 * (r / (OS_RAND_MAX + 1.0))); } else { r = __rand(&td->__rwmix_state); v = 1 + (int) (100.0 * (r / (FRAND_MAX + 1.0))); } if (v <= td->o.rwmix[DDIR_READ]) return DDIR_READ; return DDIR_WRITE; } void io_u_quiesce(struct thread_data *td) { /* * We are going to sleep, ensure that we flush anything pending as * not to skew our latency numbers. * * Changed to only monitor 'in flight' requests here instead of the * td->cur_depth, b/c td->cur_depth does not accurately represent * io's that have been actually submitted to an async engine, * and cur_depth is meaningless for sync engines. */ while (td->io_u_in_flight) { int fio_unused ret; ret = io_u_queued_complete(td, 1, NULL); } } static enum fio_ddir rate_ddir(struct thread_data *td, enum fio_ddir ddir) { enum fio_ddir odir = ddir ^ 1; struct timeval t; long usec; assert(ddir_rw(ddir)); if (td->rate_pending_usleep[ddir] <= 0) return ddir; /* * We have too much pending sleep in this direction. See if we * should switch. */ if (td_rw(td) && td->o.rwmix[odir]) { /* * Other direction does not have too much pending, switch */ if (td->rate_pending_usleep[odir] < 100000) return odir; /* * Both directions have pending sleep. Sleep the minimum time * and deduct from both. */ if (td->rate_pending_usleep[ddir] <= td->rate_pending_usleep[odir]) { usec = td->rate_pending_usleep[ddir]; } else { usec = td->rate_pending_usleep[odir]; ddir = odir; } } else usec = td->rate_pending_usleep[ddir]; io_u_quiesce(td); fio_gettime(&t, NULL); usec_sleep(td, usec); usec = utime_since_now(&t); td->rate_pending_usleep[ddir] -= usec; odir = ddir ^ 1; if (td_rw(td) && __should_check_rate(td, odir)) td->rate_pending_usleep[odir] -= usec; if (ddir_trim(ddir)) return ddir; return ddir; } /* * Return the data direction for the next io_u. If the job is a * mixed read/write workload, check the rwmix cycle and switch if * necessary. */ static enum fio_ddir get_rw_ddir(struct thread_data *td) { enum fio_ddir ddir; /* * see if it's time to fsync */ if (td->o.fsync_blocks && !(td->io_issues[DDIR_WRITE] % td->o.fsync_blocks) && td->io_issues[DDIR_WRITE] && should_fsync(td)) return DDIR_SYNC; /* * see if it's time to fdatasync */ if (td->o.fdatasync_blocks && !(td->io_issues[DDIR_WRITE] % td->o.fdatasync_blocks) && td->io_issues[DDIR_WRITE] && should_fsync(td)) return DDIR_DATASYNC; /* * see if it's time to sync_file_range */ if (td->sync_file_range_nr && !(td->io_issues[DDIR_WRITE] % td->sync_file_range_nr) && td->io_issues[DDIR_WRITE] && should_fsync(td)) return DDIR_SYNC_FILE_RANGE; if (td_rw(td)) { /* * Check if it's time to seed a new data direction. */ if (td->io_issues[td->rwmix_ddir] >= td->rwmix_issues) { /* * Put a top limit on how many bytes we do for * one data direction, to avoid overflowing the * ranges too much */ ddir = get_rand_ddir(td); if (ddir != td->rwmix_ddir) set_rwmix_bytes(td); td->rwmix_ddir = ddir; } ddir = td->rwmix_ddir; } else if (td_read(td)) ddir = DDIR_READ; else if (td_write(td)) ddir = DDIR_WRITE; else ddir = DDIR_TRIM; td->rwmix_ddir = rate_ddir(td, ddir); return td->rwmix_ddir; } static void set_rw_ddir(struct thread_data *td, struct io_u *io_u) { io_u->ddir = io_u->acct_ddir = get_rw_ddir(td); if (io_u->ddir == DDIR_WRITE && (td->io_ops->flags & FIO_BARRIER) && td->o.barrier_blocks && !(td->io_issues[DDIR_WRITE] % td->o.barrier_blocks) && td->io_issues[DDIR_WRITE]) io_u->flags |= IO_U_F_BARRIER; } void put_file_log(struct thread_data *td, struct fio_file *f) { int ret = put_file(td, f); if (ret) td_verror(td, ret, "file close"); } void put_io_u(struct thread_data *td, struct io_u *io_u) { td_io_u_lock(td); if (io_u->file && !(io_u->flags & IO_U_F_FREE_DEF)) put_file_log(td, io_u->file); io_u->file = NULL; io_u->flags &= ~IO_U_F_FREE_DEF; io_u->flags |= IO_U_F_FREE; if (io_u->flags & IO_U_F_IN_CUR_DEPTH) td->cur_depth--; io_u_qpush(&td->io_u_freelist, io_u); td_io_u_unlock(td); td_io_u_free_notify(td); } void clear_io_u(struct thread_data *td, struct io_u *io_u) { io_u->flags &= ~IO_U_F_FLIGHT; put_io_u(td, io_u); } void requeue_io_u(struct thread_data *td, struct io_u **io_u) { struct io_u *__io_u = *io_u; enum fio_ddir ddir = acct_ddir(__io_u); dprint(FD_IO, "requeue %p\n", __io_u); td_io_u_lock(td); __io_u->flags |= IO_U_F_FREE; if ((__io_u->flags & IO_U_F_FLIGHT) && ddir_rw(ddir)) td->io_issues[ddir]--; __io_u->flags &= ~IO_U_F_FLIGHT; if (__io_u->flags & IO_U_F_IN_CUR_DEPTH) td->cur_depth--; io_u_rpush(&td->io_u_requeues, __io_u); td_io_u_unlock(td); *io_u = NULL; } static int fill_io_u(struct thread_data *td, struct io_u *io_u) { unsigned int is_random; if (td->io_ops->flags & FIO_NOIO) goto out; set_rw_ddir(td, io_u); /* * fsync() or fdatasync() or trim etc, we are done */ if (!ddir_rw(io_u->ddir)) goto out; /* * See if it's time to switch to a new zone */ if (td->zone_bytes >= td->o.zone_size && td->o.zone_skip) { td->zone_bytes = 0; io_u->file->file_offset += td->o.zone_range + td->o.zone_skip; io_u->file->last_pos = io_u->file->file_offset; td->io_skip_bytes += td->o.zone_skip; } /* * No log, let the seq/rand engine retrieve the next buflen and * position. */ if (get_next_offset(td, io_u, &is_random)) { dprint(FD_IO, "io_u %p, failed getting offset\n", io_u); return 1; } io_u->buflen = get_next_buflen(td, io_u, is_random); if (!io_u->buflen) { dprint(FD_IO, "io_u %p, failed getting buflen\n", io_u); return 1; } if (io_u->offset + io_u->buflen > io_u->file->real_file_size) { dprint(FD_IO, "io_u %p, offset too large\n", io_u); dprint(FD_IO, " off=%llu/%lu > %llu\n", (unsigned long long) io_u->offset, io_u->buflen, (unsigned long long) io_u->file->real_file_size); return 1; } /* * mark entry before potentially trimming io_u */ if (td_random(td) && file_randommap(td, io_u->file)) mark_random_map(td, io_u); out: dprint_io_u(io_u, "fill_io_u"); td->zone_bytes += io_u->buflen; return 0; } static void __io_u_mark_map(unsigned int *map, unsigned int nr) { int idx = 0; switch (nr) { default: idx = 6; break; case 33 ... 64: idx = 5; break; case 17 ... 32: idx = 4; break; case 9 ... 16: idx = 3; break; case 5 ... 8: idx = 2; break; case 1 ... 4: idx = 1; case 0: break; } map[idx]++; } void io_u_mark_submit(struct thread_data *td, unsigned int nr) { __io_u_mark_map(td->ts.io_u_submit, nr); td->ts.total_submit++; } void io_u_mark_complete(struct thread_data *td, unsigned int nr) { __io_u_mark_map(td->ts.io_u_complete, nr); td->ts.total_complete++; } void io_u_mark_depth(struct thread_data *td, unsigned int nr) { int idx = 0; switch (td->cur_depth) { default: idx = 6; break; case 32 ... 63: idx = 5; break; case 16 ... 31: idx = 4; break; case 8 ... 15: idx = 3; break; case 4 ... 7: idx = 2; break; case 2 ... 3: idx = 1; case 1: break; } td->ts.io_u_map[idx] += nr; } static void io_u_mark_lat_usec(struct thread_data *td, unsigned long usec) { int idx = 0; assert(usec < 1000); switch (usec) { case 750 ... 999: idx = 9; break; case 500 ... 749: idx = 8; break; case 250 ... 499: idx = 7; break; case 100 ... 249: idx = 6; break; case 50 ... 99: idx = 5; break; case 20 ... 49: idx = 4; break; case 10 ... 19: idx = 3; break; case 4 ... 9: idx = 2; break; case 2 ... 3: idx = 1; case 0 ... 1: break; } assert(idx < FIO_IO_U_LAT_U_NR); td->ts.io_u_lat_u[idx]++; } static void io_u_mark_lat_msec(struct thread_data *td, unsigned long msec) { int idx = 0; switch (msec) { default: idx = 11; break; case 1000 ... 1999: idx = 10; break; case 750 ... 999: idx = 9; break; case 500 ... 749: idx = 8; break; case 250 ... 499: idx = 7; break; case 100 ... 249: idx = 6; break; case 50 ... 99: idx = 5; break; case 20 ... 49: idx = 4; break; case 10 ... 19: idx = 3; break; case 4 ... 9: idx = 2; break; case 2 ... 3: idx = 1; case 0 ... 1: break; } assert(idx < FIO_IO_U_LAT_M_NR); td->ts.io_u_lat_m[idx]++; } static void io_u_mark_latency(struct thread_data *td, unsigned long usec) { if (usec < 1000) io_u_mark_lat_usec(td, usec); else io_u_mark_lat_msec(td, usec / 1000); } /* * Get next file to service by choosing one at random */ static struct fio_file *get_next_file_rand(struct thread_data *td, enum fio_file_flags goodf, enum fio_file_flags badf) { struct fio_file *f; int fno; do { int opened = 0; unsigned long r; if (td->o.use_os_rand) { r = os_random_long(&td->next_file_state); fno = (unsigned int) ((double) td->o.nr_files * (r / (OS_RAND_MAX + 1.0))); } else { r = __rand(&td->__next_file_state); fno = (unsigned int) ((double) td->o.nr_files * (r / (FRAND_MAX + 1.0))); } f = td->files[fno]; if (fio_file_done(f)) continue; if (!fio_file_open(f)) { int err; err = td_io_open_file(td, f); if (err) continue; opened = 1; } if ((!goodf || (f->flags & goodf)) && !(f->flags & badf)) { dprint(FD_FILE, "get_next_file_rand: %p\n", f); return f; } if (opened) td_io_close_file(td, f); } while (1); } /* * Get next file to service by doing round robin between all available ones */ static struct fio_file *get_next_file_rr(struct thread_data *td, int goodf, int badf) { unsigned int old_next_file = td->next_file; struct fio_file *f; do { int opened = 0; f = td->files[td->next_file]; td->next_file++; if (td->next_file >= td->o.nr_files) td->next_file = 0; dprint(FD_FILE, "trying file %s %x\n", f->file_name, f->flags); if (fio_file_done(f)) { f = NULL; continue; } if (!fio_file_open(f)) { int err; err = td_io_open_file(td, f); if (err) { dprint(FD_FILE, "error %d on open of %s\n", err, f->file_name); f = NULL; continue; } opened = 1; } dprint(FD_FILE, "goodf=%x, badf=%x, ff=%x\n", goodf, badf, f->flags); if ((!goodf || (f->flags & goodf)) && !(f->flags & badf)) break; if (opened) td_io_close_file(td, f); f = NULL; } while (td->next_file != old_next_file); dprint(FD_FILE, "get_next_file_rr: %p\n", f); return f; } static struct fio_file *__get_next_file(struct thread_data *td) { struct fio_file *f; assert(td->o.nr_files <= td->files_index); if (td->nr_done_files >= td->o.nr_files) { dprint(FD_FILE, "get_next_file: nr_open=%d, nr_done=%d," " nr_files=%d\n", td->nr_open_files, td->nr_done_files, td->o.nr_files); return NULL; } f = td->file_service_file; if (f && fio_file_open(f) && !fio_file_closing(f)) { if (td->o.file_service_type == FIO_FSERVICE_SEQ) goto out; if (td->file_service_left--) goto out; } if (td->o.file_service_type == FIO_FSERVICE_RR || td->o.file_service_type == FIO_FSERVICE_SEQ) f = get_next_file_rr(td, FIO_FILE_open, FIO_FILE_closing); else f = get_next_file_rand(td, FIO_FILE_open, FIO_FILE_closing); td->file_service_file = f; td->file_service_left = td->file_service_nr - 1; out: dprint(FD_FILE, "get_next_file: %p [%s]\n", f, f->file_name); return f; } static struct fio_file *get_next_file(struct thread_data *td) { if (!(td->flags & TD_F_PROFILE_OPS)) { struct prof_io_ops *ops = &td->prof_io_ops; if (ops->get_next_file) return ops->get_next_file(td); } return __get_next_file(td); } static int set_io_u_file(struct thread_data *td, struct io_u *io_u) { struct fio_file *f; do { f = get_next_file(td); if (!f) return 1; io_u->file = f; get_file(f); if (!fill_io_u(td, io_u)) break; put_file_log(td, f); td_io_close_file(td, f); io_u->file = NULL; fio_file_set_done(f); td->nr_done_files++; dprint(FD_FILE, "%s: is done (%d of %d)\n", f->file_name, td->nr_done_files, td->o.nr_files); } while (1); return 0; } struct io_u *__get_io_u(struct thread_data *td) { struct io_u *io_u; td_io_u_lock(td); again: if (!io_u_rempty(&td->io_u_requeues)) io_u = io_u_rpop(&td->io_u_requeues); else if (!io_u_qempty(&td->io_u_freelist)) { io_u = io_u_qpop(&td->io_u_freelist); io_u->buflen = 0; io_u->resid = 0; io_u->file = NULL; io_u->end_io = NULL; } if (io_u) { assert(io_u->flags & IO_U_F_FREE); io_u->flags &= ~(IO_U_F_FREE | IO_U_F_FREE_DEF); io_u->flags &= ~(IO_U_F_TRIMMED | IO_U_F_BARRIER); io_u->flags &= ~IO_U_F_VER_LIST; io_u->error = 0; io_u->acct_ddir = -1; td->cur_depth++; io_u->flags |= IO_U_F_IN_CUR_DEPTH; } else if (td->o.verify_async) { /* * We ran out, wait for async verify threads to finish and * return one */ pthread_cond_wait(&td->free_cond, &td->io_u_lock); goto again; } td_io_u_unlock(td); return io_u; } static int check_get_trim(struct thread_data *td, struct io_u *io_u) { if (!(td->flags & TD_F_TRIM_BACKLOG)) return 0; if (td->trim_entries) { int get_trim = 0; if (td->trim_batch) { td->trim_batch--; get_trim = 1; } else if (!(td->io_hist_len % td->o.trim_backlog) && td->last_ddir != DDIR_READ) { td->trim_batch = td->o.trim_batch; if (!td->trim_batch) td->trim_batch = td->o.trim_backlog; get_trim = 1; } if (get_trim && !get_next_trim(td, io_u)) return 1; } return 0; } static int check_get_verify(struct thread_data *td, struct io_u *io_u) { if (!(td->flags & TD_F_VER_BACKLOG)) return 0; if (td->io_hist_len) { int get_verify = 0; if (td->verify_batch) get_verify = 1; else if (!(td->io_hist_len % td->o.verify_backlog) && td->last_ddir != DDIR_READ) { td->verify_batch = td->o.verify_batch; if (!td->verify_batch) td->verify_batch = td->o.verify_backlog; get_verify = 1; } if (get_verify && !get_next_verify(td, io_u)) { td->verify_batch--; return 1; } } return 0; } /* * Fill offset and start time into the buffer content, to prevent too * easy compressible data for simple de-dupe attempts. Do this for every * 512b block in the range, since that should be the smallest block size * we can expect from a device. */ static void small_content_scramble(struct io_u *io_u) { unsigned int i, nr_blocks = io_u->buflen / 512; uint64_t boffset; unsigned int offset; void *p, *end; if (!nr_blocks) return; p = io_u->xfer_buf; boffset = io_u->offset; io_u->buf_filled_len = 0; for (i = 0; i < nr_blocks; i++) { /* * Fill the byte offset into a "random" start offset of * the buffer, given by the product of the usec time * and the actual offset. */ offset = (io_u->start_time.tv_usec ^ boffset) & 511; offset &= ~(sizeof(uint64_t) - 1); if (offset >= 512 - sizeof(uint64_t)) offset -= sizeof(uint64_t); memcpy(p + offset, &boffset, sizeof(boffset)); end = p + 512 - sizeof(io_u->start_time); memcpy(end, &io_u->start_time, sizeof(io_u->start_time)); p += 512; boffset += 512; } } /* * Return an io_u to be processed. Gets a buflen and offset, sets direction, * etc. The returned io_u is fully ready to be prepped and submitted. */ struct io_u *get_io_u(struct thread_data *td) { struct fio_file *f; struct io_u *io_u; int do_scramble = 0; io_u = __get_io_u(td); if (!io_u) { dprint(FD_IO, "__get_io_u failed\n"); return NULL; } if (check_get_verify(td, io_u)) goto out; if (check_get_trim(td, io_u)) goto out; /* * from a requeue, io_u already setup */ if (io_u->file) goto out; /* * If using an iolog, grab next piece if any available. */ if (td->flags & TD_F_READ_IOLOG) { if (read_iolog_get(td, io_u)) goto err_put; } else if (set_io_u_file(td, io_u)) { dprint(FD_IO, "io_u %p, setting file failed\n", io_u); goto err_put; } f = io_u->file; assert(fio_file_open(f)); if (ddir_rw(io_u->ddir)) { if (!io_u->buflen && !(td->io_ops->flags & FIO_NOIO)) { dprint(FD_IO, "get_io_u: zero buflen on %p\n", io_u); goto err_put; } f->last_start = io_u->offset; f->last_pos = io_u->offset + io_u->buflen; if (io_u->ddir == DDIR_WRITE) { if (td->flags & TD_F_REFILL_BUFFERS) { io_u_fill_buffer(td, io_u, io_u->xfer_buflen, io_u->xfer_buflen); } else if (td->flags & TD_F_SCRAMBLE_BUFFERS) do_scramble = 1; if (td->flags & TD_F_VER_NONE) { populate_verify_io_u(td, io_u); do_scramble = 0; } } else if (io_u->ddir == DDIR_READ) { /* * Reset the buf_filled parameters so next time if the * buffer is used for writes it is refilled. */ io_u->buf_filled_len = 0; } } /* * Set io data pointers. */ io_u->xfer_buf = io_u->buf; io_u->xfer_buflen = io_u->buflen; out: assert(io_u->file); if (!td_io_prep(td, io_u)) { if (!td->o.disable_slat) fio_gettime(&io_u->start_time, NULL); if (do_scramble) small_content_scramble(io_u); return io_u; } err_put: dprint(FD_IO, "get_io_u failed\n"); put_io_u(td, io_u); return NULL; } void io_u_log_error(struct thread_data *td, struct io_u *io_u) { enum error_type_bit eb = td_error_type(io_u->ddir, io_u->error); const char *msg[] = { "read", "write", "sync", "datasync", "sync_file_range", "wait", "trim" }; if (td_non_fatal_error(td, eb, io_u->error) && !td->o.error_dump) return; log_err("fio: io_u error"); if (io_u->file) log_err(" on file %s", io_u->file->file_name); log_err(": %s\n", strerror(io_u->error)); log_err(" %s offset=%llu, buflen=%lu\n", msg[io_u->ddir], io_u->offset, io_u->xfer_buflen); if (!td->error) td_verror(td, io_u->error, "io_u error"); } static void account_io_completion(struct thread_data *td, struct io_u *io_u, struct io_completion_data *icd, const enum fio_ddir idx, unsigned int bytes) { unsigned long lusec = 0; if (!td->o.disable_clat || !td->o.disable_bw) lusec = utime_since(&io_u->issue_time, &icd->time); if (!td->o.disable_lat) { unsigned long tusec; tusec = utime_since(&io_u->start_time, &icd->time); add_lat_sample(td, idx, tusec, bytes); if (td->flags & TD_F_PROFILE_OPS) { struct prof_io_ops *ops = &td->prof_io_ops; if (ops->io_u_lat) icd->error = ops->io_u_lat(td, tusec); } if (td->o.max_latency && tusec > td->o.max_latency) { if (!td->error) log_err("fio: latency of %lu usec exceeds specified max (%u usec)\n", tusec, td->o.max_latency); td_verror(td, ETIMEDOUT, "max latency exceeded"); icd->error = ETIMEDOUT; } } if (!td->o.disable_clat) { add_clat_sample(td, idx, lusec, bytes); io_u_mark_latency(td, lusec); } if (!td->o.disable_bw) add_bw_sample(td, idx, bytes, &icd->time); add_iops_sample(td, idx, bytes, &icd->time); if (td->o.number_ios && !--td->o.number_ios) td->done = 1; } static long long usec_for_io(struct thread_data *td, enum fio_ddir ddir) { uint64_t secs, remainder, bps, bytes; bytes = td->this_io_bytes[ddir]; bps = td->rate_bps[ddir]; secs = bytes / bps; remainder = bytes % bps; return remainder * 1000000 / bps + secs * 1000000; } static void io_completed(struct thread_data *td, struct io_u *io_u, struct io_completion_data *icd) { struct fio_file *f; dprint_io_u(io_u, "io complete"); td_io_u_lock(td); assert(io_u->flags & IO_U_F_FLIGHT); io_u->flags &= ~(IO_U_F_FLIGHT | IO_U_F_BUSY_OK); td_io_u_unlock(td); if (ddir_sync(io_u->ddir)) { td->last_was_sync = 1; f = io_u->file; if (f) { f->first_write = -1ULL; f->last_write = -1ULL; } return; } td->last_was_sync = 0; td->last_ddir = io_u->ddir; if (!io_u->error && ddir_rw(io_u->ddir)) { unsigned int bytes = io_u->buflen - io_u->resid; const enum fio_ddir idx = io_u->ddir; const enum fio_ddir odx = io_u->ddir ^ 1; int ret; td->io_blocks[idx]++; td->this_io_blocks[idx]++; td->io_bytes[idx] += bytes; if (!(io_u->flags & IO_U_F_VER_LIST)) td->this_io_bytes[idx] += bytes; if (idx == DDIR_WRITE) { f = io_u->file; if (f) { if (f->first_write == -1ULL || io_u->offset < f->first_write) f->first_write = io_u->offset; if (f->last_write == -1ULL || ((io_u->offset + bytes) > f->last_write)) f->last_write = io_u->offset + bytes; } } if (ramp_time_over(td) && (td->runstate == TD_RUNNING || td->runstate == TD_VERIFYING)) { account_io_completion(td, io_u, icd, idx, bytes); if (__should_check_rate(td, idx)) { td->rate_pending_usleep[idx] = (usec_for_io(td, idx) - utime_since_now(&td->start)); } if (idx != DDIR_TRIM && __should_check_rate(td, odx)) td->rate_pending_usleep[odx] = (usec_for_io(td, odx) - utime_since_now(&td->start)); } if (td_write(td) && idx == DDIR_WRITE && td->o.do_verify && td->o.verify != VERIFY_NONE && !td->o.experimental_verify) log_io_piece(td, io_u); icd->bytes_done[idx] += bytes; if (io_u->end_io) { ret = io_u->end_io(td, io_u); if (ret && !icd->error) icd->error = ret; } } else if (io_u->error) { icd->error = io_u->error; io_u_log_error(td, io_u); } if (icd->error) { enum error_type_bit eb = td_error_type(io_u->ddir, icd->error); if (!td_non_fatal_error(td, eb, icd->error)) return; /* * If there is a non_fatal error, then add to the error count * and clear all the errors. */ update_error_count(td, icd->error); td_clear_error(td); icd->error = 0; io_u->error = 0; } } static void init_icd(struct thread_data *td, struct io_completion_data *icd, int nr) { int ddir; if (!td->o.disable_clat || !td->o.disable_bw) fio_gettime(&icd->time, NULL); icd->nr = nr; icd->error = 0; for (ddir = DDIR_READ; ddir < DDIR_RWDIR_CNT; ddir++) icd->bytes_done[ddir] = 0; } static void ios_completed(struct thread_data *td, struct io_completion_data *icd) { struct io_u *io_u; int i; for (i = 0; i < icd->nr; i++) { io_u = td->io_ops->event(td, i); io_completed(td, io_u, icd); if (!(io_u->flags & IO_U_F_FREE_DEF)) put_io_u(td, io_u); } } /* * Complete a single io_u for the sync engines. */ int io_u_sync_complete(struct thread_data *td, struct io_u *io_u, uint64_t *bytes) { struct io_completion_data icd; init_icd(td, &icd, 1); io_completed(td, io_u, &icd); if (!(io_u->flags & IO_U_F_FREE_DEF)) put_io_u(td, io_u); if (icd.error) { td_verror(td, icd.error, "io_u_sync_complete"); return -1; } if (bytes) { int ddir; for (ddir = DDIR_READ; ddir < DDIR_RWDIR_CNT; ddir++) bytes[ddir] += icd.bytes_done[ddir]; } return 0; } /* * Called to complete min_events number of io for the async engines. */ int io_u_queued_complete(struct thread_data *td, int min_evts, uint64_t *bytes) { struct io_completion_data icd; struct timespec *tvp = NULL; int ret; struct timespec ts = { .tv_sec = 0, .tv_nsec = 0, }; dprint(FD_IO, "io_u_queued_completed: min=%d\n", min_evts); if (!min_evts) tvp = &ts; ret = td_io_getevents(td, min_evts, td->o.iodepth_batch_complete, tvp); if (ret < 0) { td_verror(td, -ret, "td_io_getevents"); return ret; } else if (!ret) return ret; init_icd(td, &icd, ret); ios_completed(td, &icd); if (icd.error) { td_verror(td, icd.error, "io_u_queued_complete"); return -1; } if (bytes) { int ddir; for (ddir = DDIR_READ; ddir < DDIR_RWDIR_CNT; ddir++) bytes[ddir] += icd.bytes_done[ddir]; } return 0; } /* * Call when io_u is really queued, to update the submission latency. */ void io_u_queued(struct thread_data *td, struct io_u *io_u) { if (!td->o.disable_slat) { unsigned long slat_time; slat_time = utime_since(&io_u->start_time, &io_u->issue_time); add_slat_sample(td, io_u->ddir, slat_time, io_u->xfer_buflen); } } void fill_io_buffer(struct thread_data *td, void *buf, unsigned int min_write, unsigned int max_bs) { if (!td->o.zero_buffers) { unsigned int perc = td->o.compress_percentage; if (perc) { unsigned int seg = min_write; seg = min(min_write, td->o.compress_chunk); if (!seg) seg = min_write; fill_random_buf_percentage(&td->buf_state, buf, perc, seg, max_bs); } else fill_random_buf(&td->buf_state, buf, max_bs); } else memset(buf, 0, max_bs); } /* * "randomly" fill the buffer contents */ void io_u_fill_buffer(struct thread_data *td, struct io_u *io_u, unsigned int min_write, unsigned int max_bs) { io_u->buf_filled_len = 0; fill_io_buffer(td, io_u->buf, min_write, max_bs); } fio-2.1.3/io_u_queue.c000066400000000000000000000013721222032232000145640ustar00rootroot00000000000000#include #include "io_u_queue.h" int io_u_qinit(struct io_u_queue *q, unsigned int nr) { q->io_us = calloc(sizeof(struct io_u *), nr); if (!q->io_us) return 1; q->nr = 0; return 0; } void io_u_qexit(struct io_u_queue *q) { free(q->io_us); } int io_u_rinit(struct io_u_ring *ring, unsigned int nr) { ring->max = nr + 1; if (ring->max & (ring->max - 1)) { ring->max--; ring->max |= ring->max >> 1; ring->max |= ring->max >> 2; ring->max |= ring->max >> 4; ring->max |= ring->max >> 8; ring->max |= ring->max >> 16; ring->max++; } ring->ring = calloc(sizeof(struct io_u *), ring->max); if (!ring->ring) return 1; ring->head = ring->tail = 0; return 0; } void io_u_rexit(struct io_u_ring *ring) { free(ring->ring); } fio-2.1.3/io_u_queue.h000066400000000000000000000025121222032232000145660ustar00rootroot00000000000000#ifndef FIO_IO_U_QUEUE #define FIO_IO_U_QUEUE #include struct io_u; struct io_u_queue { struct io_u **io_us; unsigned int nr; }; static inline struct io_u *io_u_qpop(struct io_u_queue *q) { if (q->nr) return q->io_us[--q->nr]; return NULL; } static inline void io_u_qpush(struct io_u_queue *q, struct io_u *io_u) { q->io_us[q->nr++] = io_u; } static inline int io_u_qempty(struct io_u_queue *q) { return !q->nr; } #define io_u_qiter(q, io_u, i) \ for (i = 0, io_u = (q)->io_us[0]; i < (q)->nr; i++, io_u = (q)->io_us[i]) int io_u_qinit(struct io_u_queue *q, unsigned int nr); void io_u_qexit(struct io_u_queue *q); struct io_u_ring { unsigned int head; unsigned int tail; unsigned int max; struct io_u **ring; }; int io_u_rinit(struct io_u_ring *ring, unsigned int nr); void io_u_rexit(struct io_u_ring *ring); static inline void io_u_rpush(struct io_u_ring *r, struct io_u *io_u) { if (r->head + 1 != r->tail) { r->ring[r->head] = io_u; r->head = (r->head + 1) & (r->max - 1); return; } assert(0); } static inline struct io_u *io_u_rpop(struct io_u_ring *r) { if (r->head != r->tail) { struct io_u *io_u = r->ring[r->tail]; r->tail = (r->tail + 1) & (r->max - 1); return io_u; } return NULL; } static inline int io_u_rempty(struct io_u_ring *ring) { return ring->head == ring->tail; } #endif fio-2.1.3/ioengine.h000066400000000000000000000150071222032232000142270ustar00rootroot00000000000000#ifndef FIO_IOENGINE_H #define FIO_IOENGINE_H #include "compiler/compiler.h" #include "os/os.h" #include "log.h" #include "io_ddir.h" #include "debug.h" #include "file.h" #ifdef CONFIG_LIBAIO #include #endif #ifdef CONFIG_GUASI #include #endif #define FIO_IOOPS_VERSION 16 enum { IO_U_F_FREE = 1 << 0, IO_U_F_FLIGHT = 1 << 1, IO_U_F_FREE_DEF = 1 << 2, IO_U_F_IN_CUR_DEPTH = 1 << 3, IO_U_F_BUSY_OK = 1 << 4, IO_U_F_TRIMMED = 1 << 5, IO_U_F_BARRIER = 1 << 6, IO_U_F_VER_LIST = 1 << 7, }; /* * The io unit */ struct io_u { struct timeval start_time; struct timeval issue_time; struct fio_file *file; unsigned int flags; enum fio_ddir ddir; /* * For replay workloads, we may want to account as a different * IO type than what is being submitted. */ enum fio_ddir acct_ddir; /* * Allocated/set buffer and length */ unsigned long buflen; unsigned long long offset; void *buf; /* * Initial seed for generating the buffer contents */ unsigned long rand_seed; /* * IO engine state, may be different from above when we get * partial transfers / residual data counts */ void *xfer_buf; unsigned long xfer_buflen; /* * Parameter related to pre-filled buffers and * their size to handle variable block sizes. */ unsigned long buf_filled_len; union { #ifdef CONFIG_LIBAIO struct iocb iocb; #endif #ifdef CONFIG_POSIXAIO os_aiocb_t aiocb; #endif #ifdef FIO_HAVE_SGIO struct sg_io_hdr hdr; #endif #ifdef CONFIG_GUASI guasi_req_t greq; #endif #ifdef CONFIG_SOLARISAIO aio_result_t resultp; #endif #ifdef FIO_HAVE_BINJECT struct b_user_cmd buc; #endif #ifdef CONFIG_RDMA struct ibv_mr *mr; #endif void *mmap_data; }; unsigned int resid; unsigned int error; /* * io engine private data */ union { unsigned int index; unsigned int seen; void *engine_data; }; struct flist_head verify_list; /* * Callback for io completion */ int (*end_io)(struct thread_data *, struct io_u *); }; /* * io_ops->queue() return values */ enum { FIO_Q_COMPLETED = 0, /* completed sync */ FIO_Q_QUEUED = 1, /* queued, will complete async */ FIO_Q_BUSY = 2, /* no more room, call ->commit() */ }; struct ioengine_ops { struct flist_head list; char name[16]; int version; int flags; int (*setup)(struct thread_data *); int (*init)(struct thread_data *); int (*prep)(struct thread_data *, struct io_u *); int (*queue)(struct thread_data *, struct io_u *); int (*commit)(struct thread_data *); int (*getevents)(struct thread_data *, unsigned int, unsigned int, struct timespec *); struct io_u *(*event)(struct thread_data *, int); int (*cancel)(struct thread_data *, struct io_u *); void (*cleanup)(struct thread_data *); int (*open_file)(struct thread_data *, struct fio_file *); int (*close_file)(struct thread_data *, struct fio_file *); int (*get_file_size)(struct thread_data *, struct fio_file *); void (*terminate)(struct thread_data *); int (*io_u_init)(struct thread_data *, struct io_u *); void (*io_u_free)(struct thread_data *, struct io_u *); int option_struct_size; struct fio_option *options; void *data; void *dlhandle; }; enum fio_ioengine_flags { FIO_SYNCIO = 1 << 0, /* io engine has synchronous ->queue */ FIO_RAWIO = 1 << 1, /* some sort of direct/raw io */ FIO_DISKLESSIO = 1 << 2, /* no disk involved */ FIO_NOEXTEND = 1 << 3, /* engine can't extend file */ FIO_NODISKUTIL = 1 << 4, /* diskutil can't handle filename */ FIO_UNIDIR = 1 << 5, /* engine is uni-directional */ FIO_NOIO = 1 << 6, /* thread does only pseudo IO */ FIO_PIPEIO = 1 << 7, /* input/output no seekable */ FIO_BARRIER = 1 << 8, /* engine supports barriers */ FIO_MEMALIGN = 1 << 9, /* engine wants aligned memory */ FIO_BIT_BASED = 1 << 10, /* engine uses a bit base (e.g. uses Kbit as opposed to KB) */ }; /* * io engine entry points */ extern int __must_check td_io_init(struct thread_data *); extern int __must_check td_io_prep(struct thread_data *, struct io_u *); extern int __must_check td_io_queue(struct thread_data *, struct io_u *); extern int __must_check td_io_sync(struct thread_data *, struct fio_file *); extern int __must_check td_io_getevents(struct thread_data *, unsigned int, unsigned int, struct timespec *); extern int __must_check td_io_commit(struct thread_data *); extern int __must_check td_io_open_file(struct thread_data *, struct fio_file *); extern int td_io_close_file(struct thread_data *, struct fio_file *); extern int __must_check td_io_get_file_size(struct thread_data *, struct fio_file *); extern struct ioengine_ops *load_ioengine(struct thread_data *, const char *); extern void register_ioengine(struct ioengine_ops *); extern void unregister_ioengine(struct ioengine_ops *); extern void free_ioengine(struct thread_data *); extern void close_ioengine(struct thread_data *); extern int fio_show_ioengine_help(const char *engine); /* * io unit handling */ #define queue_full(td) io_u_qempty(&(td)->io_u_freelist) extern struct io_u *__get_io_u(struct thread_data *); extern struct io_u *get_io_u(struct thread_data *); extern void put_io_u(struct thread_data *, struct io_u *); extern void clear_io_u(struct thread_data *, struct io_u *); extern void requeue_io_u(struct thread_data *, struct io_u **); extern int __must_check io_u_sync_complete(struct thread_data *, struct io_u *, uint64_t *); extern int __must_check io_u_queued_complete(struct thread_data *, int, uint64_t *); extern void io_u_queued(struct thread_data *, struct io_u *); extern void io_u_quiesce(struct thread_data *); extern void io_u_log_error(struct thread_data *, struct io_u *); extern void io_u_mark_depth(struct thread_data *, unsigned int); extern void fill_io_buffer(struct thread_data *, void *, unsigned int, unsigned int); extern void io_u_fill_buffer(struct thread_data *td, struct io_u *, unsigned int, unsigned int); void io_u_mark_complete(struct thread_data *, unsigned int); void io_u_mark_submit(struct thread_data *, unsigned int); int do_io_u_sync(struct thread_data *, struct io_u *); int do_io_u_trim(struct thread_data *, struct io_u *); #ifdef FIO_INC_DEBUG static inline void dprint_io_u(struct io_u *io_u, const char *p) { struct fio_file *f = io_u->file; dprint(FD_IO, "%s: io_u %p: off=%llu/len=%lu/ddir=%d", p, io_u, (unsigned long long) io_u->offset, io_u->buflen, io_u->ddir); if (fio_debug & (1 << FD_IO)) { if (f) log_info("/%s", f->file_name); log_info("\n"); } } #else #define dprint_io_u(io_u, p) #endif static inline enum fio_ddir acct_ddir(struct io_u *io_u) { if (io_u->acct_ddir != -1) return io_u->acct_ddir; return io_u->ddir; } #endif fio-2.1.3/ioengines.c000066400000000000000000000277201222032232000144120ustar00rootroot00000000000000/* * The io parts of the fio tool, includes workers for sync and mmap'ed * io, as well as both posix and linux libaio support. * * sync io is implemented on top of aio. * * This is not really specific to fio, if the get_io_u/put_io_u and * structures was pulled into this as well it would be a perfectly * generic io engine that could be used for other projects. * */ #include #include #include #include #include #include #include #include "fio.h" #include "diskutil.h" static FLIST_HEAD(engine_list); static int check_engine_ops(struct ioengine_ops *ops) { if (ops->version != FIO_IOOPS_VERSION) { log_err("bad ioops version %d (want %d)\n", ops->version, FIO_IOOPS_VERSION); return 1; } if (!ops->queue) { log_err("%s: no queue handler\n", ops->name); return 1; } /* * sync engines only need a ->queue() */ if (ops->flags & FIO_SYNCIO) return 0; if (!ops->event) { log_err("%s: no event handler\n", ops->name); return 1; } if (!ops->getevents) { log_err("%s: no getevents handler\n", ops->name); return 1; } if (!ops->queue) { log_err("%s: no queue handler\n", ops->name); return 1; } return 0; } void unregister_ioengine(struct ioengine_ops *ops) { dprint(FD_IO, "ioengine %s unregistered\n", ops->name); flist_del(&ops->list); INIT_FLIST_HEAD(&ops->list); } void register_ioengine(struct ioengine_ops *ops) { dprint(FD_IO, "ioengine %s registered\n", ops->name); INIT_FLIST_HEAD(&ops->list); flist_add_tail(&ops->list, &engine_list); } static struct ioengine_ops *find_ioengine(const char *name) { struct ioengine_ops *ops; struct flist_head *entry; flist_for_each(entry, &engine_list) { ops = flist_entry(entry, struct ioengine_ops, list); if (!strcmp(name, ops->name)) return ops; } return NULL; } static struct ioengine_ops *dlopen_ioengine(struct thread_data *td, const char *engine_lib) { struct ioengine_ops *ops; void *dlhandle; dprint(FD_IO, "dload engine %s\n", engine_lib); dlerror(); dlhandle = dlopen(engine_lib, RTLD_LAZY); if (!dlhandle) { td_vmsg(td, -1, dlerror(), "dlopen"); return NULL; } /* * Unlike the included modules, external engines should have a * non-static ioengine structure that we can reference. */ ops = dlsym(dlhandle, engine_lib); if (!ops) ops = dlsym(dlhandle, "ioengine"); if (!ops) { td_vmsg(td, -1, dlerror(), "dlsym"); dlclose(dlhandle); return NULL; } ops->dlhandle = dlhandle; return ops; } struct ioengine_ops *load_ioengine(struct thread_data *td, const char *name) { struct ioengine_ops *ops, *ret; char engine[16]; dprint(FD_IO, "load ioengine %s\n", name); strncpy(engine, name, sizeof(engine) - 1); /* * linux libaio has alias names, so convert to what we want */ if (!strncmp(engine, "linuxaio", 8) || !strncmp(engine, "aio", 3)) strcpy(engine, "libaio"); ops = find_ioengine(engine); if (!ops) ops = dlopen_ioengine(td, name); if (!ops) { log_err("fio: engine %s not loadable\n", name); return NULL; } /* * Check that the required methods are there. */ if (check_engine_ops(ops)) return NULL; ret = malloc(sizeof(*ret)); memcpy(ret, ops, sizeof(*ret)); ret->data = NULL; return ret; } /* * For cleaning up an ioengine which never made it to init(). */ void free_ioengine(struct thread_data *td) { dprint(FD_IO, "free ioengine %s\n", td->io_ops->name); if (td->eo && td->io_ops->options) { options_free(td->io_ops->options, td->eo); free(td->eo); td->eo = NULL; } if (td->io_ops->dlhandle) dlclose(td->io_ops->dlhandle); free(td->io_ops); td->io_ops = NULL; } void close_ioengine(struct thread_data *td) { dprint(FD_IO, "close ioengine %s\n", td->io_ops->name); if (td->io_ops->cleanup) { td->io_ops->cleanup(td); td->io_ops->data = NULL; } free_ioengine(td); } int td_io_prep(struct thread_data *td, struct io_u *io_u) { dprint_io_u(io_u, "prep"); fio_ro_check(td, io_u); lock_file(td, io_u->file, io_u->ddir); if (td->io_ops->prep) { int ret = td->io_ops->prep(td, io_u); dprint(FD_IO, "->prep(%p)=%d\n", io_u, ret); if (ret) unlock_file(td, io_u->file); return ret; } return 0; } int td_io_getevents(struct thread_data *td, unsigned int min, unsigned int max, struct timespec *t) { int r = 0; /* * For ioengine=rdma one side operation RDMA_WRITE or RDMA_READ, * server side gets a message from the client * side that the task is finished, and * td->done is set to 1 after td_io_commit(). In this case, * there is no need to reap complete event in server side. */ if (td->done) return 0; if (min > 0 && td->io_ops->commit) { r = td->io_ops->commit(td); if (r < 0) goto out; } if (max > td->cur_depth) max = td->cur_depth; if (min > max) max = min; r = 0; if (max && td->io_ops->getevents) r = td->io_ops->getevents(td, min, max, t); out: if (r >= 0) { /* * Reflect that our submitted requests were retrieved with * whatever OS async calls are in the underlying engine. */ td->io_u_in_flight -= r; io_u_mark_complete(td, r); } else td_verror(td, r, "get_events"); dprint(FD_IO, "getevents: %d\n", r); return r; } int td_io_queue(struct thread_data *td, struct io_u *io_u) { int ret; dprint_io_u(io_u, "queue"); fio_ro_check(td, io_u); assert((io_u->flags & IO_U_F_FLIGHT) == 0); io_u->flags |= IO_U_F_FLIGHT; assert(fio_file_open(io_u->file)); /* * If using a write iolog, store this entry. */ log_io_u(td, io_u); io_u->error = 0; io_u->resid = 0; if (td->io_ops->flags & FIO_SYNCIO) { if (fio_fill_issue_time(td)) fio_gettime(&io_u->issue_time, NULL); /* * only used for iolog */ if (td->o.read_iolog_file) memcpy(&td->last_issue, &io_u->issue_time, sizeof(struct timeval)); } if (ddir_rw(acct_ddir(io_u))) td->io_issues[acct_ddir(io_u)]++; ret = td->io_ops->queue(td, io_u); unlock_file(td, io_u->file); /* * If an error was seen and the io engine didn't propagate it * back to 'td', do so. */ if (io_u->error && !td->error) td_verror(td, io_u->error, "td_io_queue"); /* * Add warning for O_DIRECT so that users have an easier time * spotting potentially bad alignment. If this triggers for the first * IO, then it's likely an alignment problem or because the host fs * does not support O_DIRECT */ if (io_u->error == EINVAL && td->io_issues[io_u->ddir & 1] == 1 && td->o.odirect) { log_info("fio: first direct IO errored. File system may not " "support direct IO, or iomem_align= is bad.\n"); } if (!td->io_ops->commit || ddir_trim(io_u->ddir)) { io_u_mark_submit(td, 1); io_u_mark_complete(td, 1); } if (ret == FIO_Q_COMPLETED) { if (ddir_rw(io_u->ddir)) { io_u_mark_depth(td, 1); td->ts.total_io_u[io_u->ddir]++; } } else if (ret == FIO_Q_QUEUED) { int r; if (ddir_rw(io_u->ddir)) { td->io_u_queued++; td->ts.total_io_u[io_u->ddir]++; } if (td->io_u_queued >= td->o.iodepth_batch) { r = td_io_commit(td); if (r < 0) return r; } } if ((td->io_ops->flags & FIO_SYNCIO) == 0) { if (fio_fill_issue_time(td)) fio_gettime(&io_u->issue_time, NULL); /* * only used for iolog */ if (td->o.read_iolog_file) memcpy(&td->last_issue, &io_u->issue_time, sizeof(struct timeval)); } return ret; } int td_io_init(struct thread_data *td) { int ret = 0; if (td->io_ops->init) { ret = td->io_ops->init(td); if (ret && td->o.iodepth > 1) { log_err("fio: io engine init failed. Perhaps try" " reducing io depth?\n"); } if (!td->error) td->error = ret; } return ret; } int td_io_commit(struct thread_data *td) { int ret; dprint(FD_IO, "calling ->commit(), depth %d\n", td->cur_depth); if (!td->cur_depth || !td->io_u_queued) return 0; io_u_mark_depth(td, td->io_u_queued); if (td->io_ops->commit) { ret = td->io_ops->commit(td); if (ret) td_verror(td, -ret, "io commit"); } /* * Reflect that events were submitted as async IO requests. */ td->io_u_in_flight += td->io_u_queued; td->io_u_queued = 0; return 0; } int td_io_open_file(struct thread_data *td, struct fio_file *f) { assert(!fio_file_open(f)); assert(f->fd == -1); if (td->io_ops->open_file(td, f)) { if (td->error == EINVAL && td->o.odirect) log_err("fio: destination does not support O_DIRECT\n"); if (td->error == EMFILE) { log_err("fio: try reducing/setting openfiles (failed" " at %u of %u)\n", td->nr_open_files, td->o.nr_files); } assert(f->fd == -1); assert(!fio_file_open(f)); return 1; } fio_file_reset(td, f); fio_file_set_open(f); fio_file_clear_closing(f); disk_util_inc(f->du); td->nr_open_files++; get_file(f); if (f->filetype == FIO_TYPE_PIPE) { if (td_random(td)) { log_err("fio: can't seek on pipes (no random io)\n"); goto err; } } if (td->io_ops->flags & FIO_DISKLESSIO) goto done; if (td->o.invalidate_cache && file_invalidate_cache(td, f)) goto err; if (td->o.fadvise_hint && (f->filetype == FIO_TYPE_BD || f->filetype == FIO_TYPE_FILE)) { int flags; if (td_random(td)) flags = POSIX_FADV_RANDOM; else flags = POSIX_FADV_SEQUENTIAL; if (posix_fadvise(f->fd, f->file_offset, f->io_size, flags) < 0) { td_verror(td, errno, "fadvise"); goto err; } } #ifdef FIO_OS_DIRECTIO /* * Some OS's have a distinct call to mark the file non-buffered, * instead of using O_DIRECT (Solaris) */ if (td->o.odirect) { int ret = fio_set_odirect(f->fd); if (ret) { td_verror(td, ret, "fio_set_odirect"); log_err("fio: the file system does not seem to support direct IO\n"); goto err; } } #endif done: log_file(td, f, FIO_LOG_OPEN_FILE); return 0; err: disk_util_dec(f->du); if (td->io_ops->close_file) td->io_ops->close_file(td, f); return 1; } int td_io_close_file(struct thread_data *td, struct fio_file *f) { if (!fio_file_closing(f)) log_file(td, f, FIO_LOG_CLOSE_FILE); /* * mark as closing, do real close when last io on it has completed */ fio_file_set_closing(f); disk_util_dec(f->du); if (td->o.file_lock_mode != FILE_LOCK_NONE) unlock_file_all(td, f); return put_file(td, f); } int td_io_get_file_size(struct thread_data *td, struct fio_file *f) { if (!td->io_ops->get_file_size) return 0; return td->io_ops->get_file_size(td, f); } static int do_sync_file_range(struct thread_data *td, struct fio_file *f) { off64_t offset, nbytes; offset = f->first_write; nbytes = f->last_write - f->first_write; if (!nbytes) return 0; return sync_file_range(f->fd, offset, nbytes, td->o.sync_file_range); } int do_io_u_sync(struct thread_data *td, struct io_u *io_u) { int ret; if (io_u->ddir == DDIR_SYNC) { ret = fsync(io_u->file->fd); } else if (io_u->ddir == DDIR_DATASYNC) { #ifdef CONFIG_FDATASYNC ret = fdatasync(io_u->file->fd); #else ret = io_u->xfer_buflen; io_u->error = EINVAL; #endif } else if (io_u->ddir == DDIR_SYNC_FILE_RANGE) ret = do_sync_file_range(td, io_u->file); else { ret = io_u->xfer_buflen; io_u->error = EINVAL; } if (ret < 0) io_u->error = errno; return ret; } int do_io_u_trim(struct thread_data *td, struct io_u *io_u) { #ifndef FIO_HAVE_TRIM io_u->error = EINVAL; return 0; #else struct fio_file *f = io_u->file; int ret; ret = os_trim(f->fd, io_u->offset, io_u->xfer_buflen); if (!ret) return io_u->xfer_buflen; io_u->error = ret; return 0; #endif } int fio_show_ioengine_help(const char *engine) { struct flist_head *entry; struct thread_data td; char *sep; int ret = 1; if (!engine || !*engine) { log_info("Available IO engines:\n"); flist_for_each(entry, &engine_list) { td.io_ops = flist_entry(entry, struct ioengine_ops, list); log_info("\t%s\n", td.io_ops->name); } return 0; } sep = strchr(engine, ','); if (sep) { *sep = 0; sep++; } memset(&td, 0, sizeof(td)); td.io_ops = load_ioengine(&td, engine); if (!td.io_ops) { log_info("IO engine %s not found\n", engine); return 1; } if (td.io_ops->options) ret = show_cmd_help(td.io_ops->options, sep); else log_info("IO engine %s has no options\n", td.io_ops->name); free_ioengine(&td); return ret; } fio-2.1.3/iolog.c000066400000000000000000000265611222032232000135450ustar00rootroot00000000000000/* * Code related to writing an iolog of what a thread is doing, and to * later read that back and replay */ #include #include #include #include #include "flist.h" #include "fio.h" #include "verify.h" #include "trim.h" static const char iolog_ver2[] = "fio version 2 iolog"; void queue_io_piece(struct thread_data *td, struct io_piece *ipo) { flist_add_tail(&ipo->list, &td->io_log_list); td->total_io_size += ipo->len; } void log_io_u(struct thread_data *td, struct io_u *io_u) { const char *act[] = { "read", "write", "sync", "datasync", "sync_file_range", "wait", "trim" }; assert(io_u->ddir <= 6); if (!td->o.write_iolog_file) return; fprintf(td->iolog_f, "%s %s %llu %lu\n", io_u->file->file_name, act[io_u->ddir], io_u->offset, io_u->buflen); } void log_file(struct thread_data *td, struct fio_file *f, enum file_log_act what) { const char *act[] = { "add", "open", "close" }; assert(what < 3); if (!td->o.write_iolog_file) return; /* * this happens on the pre-open/close done before the job starts */ if (!td->iolog_f) return; fprintf(td->iolog_f, "%s %s\n", f->file_name, act[what]); } static void iolog_delay(struct thread_data *td, unsigned long delay) { unsigned long usec = utime_since_now(&td->last_issue); if (delay < usec) return; delay -= usec; /* * less than 100 usec delay, just regard it as noise */ if (delay < 100) return; usec_sleep(td, delay); } static int ipo_special(struct thread_data *td, struct io_piece *ipo) { struct fio_file *f; int ret; /* * Not a special ipo */ if (ipo->ddir != DDIR_INVAL) return 0; f = td->files[ipo->fileno]; switch (ipo->file_action) { case FIO_LOG_OPEN_FILE: ret = td_io_open_file(td, f); if (!ret) break; td_verror(td, ret, "iolog open file"); return -1; case FIO_LOG_CLOSE_FILE: td_io_close_file(td, f); break; case FIO_LOG_UNLINK_FILE: unlink(f->file_name); break; default: log_err("fio: bad file action %d\n", ipo->file_action); break; } return 1; } int read_iolog_get(struct thread_data *td, struct io_u *io_u) { struct io_piece *ipo; unsigned long elapsed; while (!flist_empty(&td->io_log_list)) { int ret; ipo = flist_entry(td->io_log_list.next, struct io_piece, list); flist_del(&ipo->list); remove_trim_entry(td, ipo); ret = ipo_special(td, ipo); if (ret < 0) { free(ipo); break; } else if (ret > 0) { free(ipo); continue; } io_u->ddir = ipo->ddir; if (ipo->ddir != DDIR_WAIT) { io_u->offset = ipo->offset; io_u->buflen = ipo->len; io_u->file = td->files[ipo->fileno]; get_file(io_u->file); dprint(FD_IO, "iolog: get %llu/%lu/%s\n", io_u->offset, io_u->buflen, io_u->file->file_name); if (ipo->delay) iolog_delay(td, ipo->delay); } else { elapsed = mtime_since_genesis(); if (ipo->delay > elapsed) usec_sleep(td, (ipo->delay - elapsed) * 1000); } free(ipo); if (io_u->ddir != DDIR_WAIT) return 0; } td->done = 1; return 1; } void prune_io_piece_log(struct thread_data *td) { struct io_piece *ipo; struct rb_node *n; while ((n = rb_first(&td->io_hist_tree)) != NULL) { ipo = rb_entry(n, struct io_piece, rb_node); rb_erase(n, &td->io_hist_tree); remove_trim_entry(td, ipo); td->io_hist_len--; free(ipo); } while (!flist_empty(&td->io_hist_list)) { ipo = flist_entry(td->io_hist_list.next, struct io_piece, list); flist_del(&ipo->list); remove_trim_entry(td, ipo); td->io_hist_len--; free(ipo); } } /* * log a successful write, so we can unwind the log for verify */ void log_io_piece(struct thread_data *td, struct io_u *io_u) { struct rb_node **p, *parent; struct io_piece *ipo, *__ipo; ipo = malloc(sizeof(struct io_piece)); init_ipo(ipo); ipo->file = io_u->file; ipo->offset = io_u->offset; ipo->len = io_u->buflen; if (io_u_should_trim(td, io_u)) { flist_add_tail(&ipo->trim_list, &td->trim_list); td->trim_entries++; } /* * We don't need to sort the entries, if: * * Sequential writes, or * Random writes that lay out the file as it goes along * * For both these cases, just reading back data in the order we * wrote it out is the fastest. * * One exception is if we don't have a random map AND we are doing * verifies, in that case we need to check for duplicate blocks and * drop the old one, which we rely on the rb insert/lookup for * handling. */ if ((!td_random(td) || !td->o.overwrite) && (file_randommap(td, ipo->file) || td->o.verify == VERIFY_NONE)) { INIT_FLIST_HEAD(&ipo->list); flist_add_tail(&ipo->list, &td->io_hist_list); ipo->flags |= IP_F_ONLIST; td->io_hist_len++; return; } RB_CLEAR_NODE(&ipo->rb_node); /* * Sort the entry into the verification list */ restart: p = &td->io_hist_tree.rb_node; parent = NULL; while (*p) { parent = *p; __ipo = rb_entry(parent, struct io_piece, rb_node); if (ipo->file < __ipo->file) p = &(*p)->rb_left; else if (ipo->file > __ipo->file) p = &(*p)->rb_right; else if (ipo->offset < __ipo->offset) p = &(*p)->rb_left; else if (ipo->offset > __ipo->offset) p = &(*p)->rb_right; else { dprint(FD_IO, "iolog: overlap %llu/%lu, %llu/%lu", __ipo->offset, __ipo->len, ipo->offset, ipo->len); td->io_hist_len--; rb_erase(parent, &td->io_hist_tree); remove_trim_entry(td, __ipo); free(__ipo); goto restart; } } rb_link_node(&ipo->rb_node, parent, p); rb_insert_color(&ipo->rb_node, &td->io_hist_tree); ipo->flags |= IP_F_ONRB; td->io_hist_len++; } void write_iolog_close(struct thread_data *td) { fflush(td->iolog_f); fclose(td->iolog_f); free(td->iolog_buf); td->iolog_f = NULL; td->iolog_buf = NULL; } /* * Read version 2 iolog data. It is enhanced to include per-file logging, * syncs, etc. */ static int read_iolog2(struct thread_data *td, FILE *f) { unsigned long long offset; unsigned int bytes; int reads, writes, waits, fileno = 0, file_action = 0; /* stupid gcc */ char *fname, *act; char *str, *p; enum fio_ddir rw; free_release_files(td); /* * Read in the read iolog and store it, reuse the infrastructure * for doing verifications. */ str = malloc(4096); fname = malloc(256+16); act = malloc(256+16); reads = writes = waits = 0; while ((p = fgets(str, 4096, f)) != NULL) { struct io_piece *ipo; int r; r = sscanf(p, "%256s %256s %llu %u", fname, act, &offset, &bytes); if (r == 4) { /* * Check action first */ if (!strcmp(act, "wait")) rw = DDIR_WAIT; else if (!strcmp(act, "read")) rw = DDIR_READ; else if (!strcmp(act, "write")) rw = DDIR_WRITE; else if (!strcmp(act, "sync")) rw = DDIR_SYNC; else if (!strcmp(act, "datasync")) rw = DDIR_DATASYNC; else if (!strcmp(act, "trim")) rw = DDIR_TRIM; else { log_err("fio: bad iolog file action: %s\n", act); continue; } fileno = get_fileno(td, fname); } else if (r == 2) { rw = DDIR_INVAL; if (!strcmp(act, "add")) { td->o.nr_files++; fileno = add_file(td, fname); file_action = FIO_LOG_ADD_FILE; continue; } else if (!strcmp(act, "open")) { fileno = get_fileno(td, fname); file_action = FIO_LOG_OPEN_FILE; } else if (!strcmp(act, "close")) { fileno = get_fileno(td, fname); file_action = FIO_LOG_CLOSE_FILE; } else { log_err("fio: bad iolog file action: %s\n", act); continue; } } else { log_err("bad iolog2: %s", p); continue; } if (rw == DDIR_READ) reads++; else if (rw == DDIR_WRITE) { /* * Don't add a write for ro mode */ if (read_only) continue; writes++; } else if (rw == DDIR_WAIT) { waits++; } else if (rw == DDIR_INVAL) { } else if (!ddir_sync(rw)) { log_err("bad ddir: %d\n", rw); continue; } /* * Make note of file */ ipo = malloc(sizeof(*ipo)); init_ipo(ipo); ipo->ddir = rw; if (rw == DDIR_WAIT) { ipo->delay = offset; } else { ipo->offset = offset; ipo->len = bytes; if (bytes > td->o.max_bs[rw]) td->o.max_bs[rw] = bytes; ipo->fileno = fileno; ipo->file_action = file_action; } queue_io_piece(td, ipo); } free(str); free(act); free(fname); if (writes && read_only) { log_err("fio: <%s> skips replay of %d writes due to" " read-only\n", td->o.name, writes); writes = 0; } if (!reads && !writes && !waits) return 1; else if (reads && !writes) td->o.td_ddir = TD_DDIR_READ; else if (!reads && writes) td->o.td_ddir = TD_DDIR_WRITE; else td->o.td_ddir = TD_DDIR_RW; return 0; } /* * open iolog, check version, and call appropriate parser */ static int init_iolog_read(struct thread_data *td) { char buffer[256], *p; FILE *f; int ret; f = fopen(td->o.read_iolog_file, "r"); if (!f) { perror("fopen read iolog"); return 1; } p = fgets(buffer, sizeof(buffer), f); if (!p) { td_verror(td, errno, "iolog read"); log_err("fio: unable to read iolog\n"); fclose(f); return 1; } /* * version 2 of the iolog stores a specific string as the * first line, check for that */ if (!strncmp(iolog_ver2, buffer, strlen(iolog_ver2))) ret = read_iolog2(td, f); else { log_err("fio: iolog version 1 is no longer supported\n"); ret = 1; } fclose(f); return ret; } /* * Set up a log for storing io patterns. */ static int init_iolog_write(struct thread_data *td) { struct fio_file *ff; FILE *f; unsigned int i; f = fopen(td->o.write_iolog_file, "a"); if (!f) { perror("fopen write iolog"); return 1; } /* * That's it for writing, setup a log buffer and we're done. */ td->iolog_f = f; td->iolog_buf = malloc(8192); setvbuf(f, td->iolog_buf, _IOFBF, 8192); /* * write our version line */ if (fprintf(f, "%s\n", iolog_ver2) < 0) { perror("iolog init\n"); return 1; } /* * add all known files */ for_each_file(td, ff, i) log_file(td, ff, FIO_LOG_ADD_FILE); return 0; } int init_iolog(struct thread_data *td) { int ret = 0; if (td->o.read_iolog_file) { /* * Check if it's a blktrace file and load that if possible. * Otherwise assume it's a normal log file and load that. */ if (is_blktrace(td->o.read_iolog_file)) ret = load_blktrace(td, td->o.read_iolog_file); else ret = init_iolog_read(td); } else if (td->o.write_iolog_file) ret = init_iolog_write(td); return ret; } void setup_log(struct io_log **log, unsigned long avg_msec, int log_type) { struct io_log *l = malloc(sizeof(*l)); memset(l, 0, sizeof(*l)); l->nr_samples = 0; l->max_samples = 1024; l->log_type = log_type; l->log = malloc(l->max_samples * sizeof(struct io_sample)); l->avg_msec = avg_msec; *log = l; } void __finish_log(struct io_log *log, const char *name) { unsigned int i; FILE *f; f = fopen(name, "a"); if (!f) { perror("fopen log"); return; } for (i = 0; i < log->nr_samples; i++) { fprintf(f, "%lu, %lu, %u, %u\n", (unsigned long) log->log[i].time, (unsigned long) log->log[i].val, log->log[i].ddir, log->log[i].bs); } fclose(f); free(log->log); free(log); } void finish_log_named(struct thread_data *td, struct io_log *log, const char *prefix, const char *postfix) { char file_name[256], *p; snprintf(file_name, sizeof(file_name), "%s_%s.log", prefix, postfix); p = basename(file_name); if (td->client_type == FIO_CLIENT_TYPE_GUI) { fio_send_iolog(td, log, p); free(log->log); free(log); } else __finish_log(log, p); } void finish_log(struct thread_data *td, struct io_log *log, const char *name) { finish_log_named(td, log, td->o.name, name); } fio-2.1.3/iolog.h000066400000000000000000000062051222032232000135430ustar00rootroot00000000000000#ifndef FIO_IOLOG_H #define FIO_IOLOG_H #include "lib/rbtree.h" #include "lib/ieee754.h" #include "flist.h" #include "ioengine.h" /* * Use for maintaining statistics */ struct io_stat { uint64_t max_val; uint64_t min_val; uint64_t samples; fio_fp64_t mean; fio_fp64_t S; }; /* * A single data sample */ struct io_sample { uint64_t time; uint64_t val; uint32_t ddir; uint32_t bs; }; enum { IO_LOG_TYPE_LAT = 1, IO_LOG_TYPE_CLAT, IO_LOG_TYPE_SLAT, IO_LOG_TYPE_BW, IO_LOG_TYPE_IOPS, }; /* * Dynamically growing data sample log */ struct io_log { /* * Entries already logged */ unsigned long nr_samples; unsigned long max_samples; struct io_sample *log; unsigned int log_type; /* * Windowed average, for logging single entries average over some * period of time. */ struct io_stat avg_window[DDIR_RWDIR_CNT]; unsigned long avg_msec; unsigned long avg_last; }; enum { IP_F_ONRB = 1, IP_F_ONLIST = 2, IP_F_TRIMMED = 4, }; /* * When logging io actions, this matches a single sent io_u */ struct io_piece { union { struct rb_node rb_node; struct flist_head list; }; struct flist_head trim_list; union { int fileno; struct fio_file *file; }; unsigned long long offset; unsigned long len; unsigned int flags; enum fio_ddir ddir; union { unsigned long delay; unsigned int file_action; }; }; /* * Log exports */ enum file_log_act { FIO_LOG_ADD_FILE, FIO_LOG_OPEN_FILE, FIO_LOG_CLOSE_FILE, FIO_LOG_UNLINK_FILE, }; struct io_u; extern int __must_check read_iolog_get(struct thread_data *, struct io_u *); extern void log_io_u(struct thread_data *, struct io_u *); extern void log_file(struct thread_data *, struct fio_file *, enum file_log_act); extern int __must_check init_iolog(struct thread_data *td); extern void log_io_piece(struct thread_data *, struct io_u *); extern void queue_io_piece(struct thread_data *, struct io_piece *); extern void prune_io_piece_log(struct thread_data *); extern void write_iolog_close(struct thread_data *); /* * Logging */ extern void add_lat_sample(struct thread_data *, enum fio_ddir, unsigned long, unsigned int); extern void add_clat_sample(struct thread_data *, enum fio_ddir, unsigned long, unsigned int); extern void add_slat_sample(struct thread_data *, enum fio_ddir, unsigned long, unsigned int); extern void add_bw_sample(struct thread_data *, enum fio_ddir, unsigned int, struct timeval *); extern void add_iops_sample(struct thread_data *, enum fio_ddir, unsigned int, struct timeval *); extern void init_disk_util(struct thread_data *); extern void update_rusage_stat(struct thread_data *); extern void setup_log(struct io_log **, unsigned long, int); extern void finish_log(struct thread_data *, struct io_log *, const char *); extern void finish_log_named(struct thread_data *, struct io_log *, const char *, const char *); extern void __finish_log(struct io_log *, const char *); extern struct io_log *agg_io_log[DDIR_RWDIR_CNT]; extern int write_bw_log; extern void add_agg_sample(unsigned long, enum fio_ddir, unsigned int); static inline void init_ipo(struct io_piece *ipo) { memset(ipo, 0, sizeof(*ipo)); INIT_FLIST_HEAD(&ipo->trim_list); } #endif fio-2.1.3/json.c000066400000000000000000000175501222032232000134030ustar00rootroot00000000000000#include #include #include #include #include #include "json.h" #include "log.h" struct json_object *json_create_object(void) { struct json_object *obj = malloc(sizeof(struct json_object)); if (obj) memset(obj, 0, sizeof(struct json_object)); return obj; } struct json_array *json_create_array(void) { struct json_array *array = malloc(sizeof(struct json_array)); if (array) memset(array, 0, sizeof(struct json_array)); return array; } static struct json_pair *json_create_pair(const char *name, struct json_value *value) { struct json_pair *pair = malloc(sizeof(struct json_pair)); if (pair) { pair->name = strdup(name); pair->value = value; value->parent_type = JSON_PARENT_TYPE_PAIR; value->parent_pair = pair; } return pair; } static struct json_value *json_create_value_int(long number) { struct json_value *value = malloc(sizeof(struct json_value)); if (value) { value->type = JSON_TYPE_INTEGER; value->integer_number = number; } return value; } static struct json_value *json_create_value_float(float number) { struct json_value *value = malloc(sizeof(struct json_value)); if (value) { value->type = JSON_TYPE_FLOAT; value->float_number = number; } return value; } static char *strdup_escape(const char *str) { const char *input = str; char *p, *ret; int escapes; if (!strlen(str)) return NULL; escapes = 0; while ((input = strpbrk(input, "\\\"")) != NULL) { escapes++; input++; } p = ret = malloc(strlen(str) + escapes + 1); while (*str) { if (*str == '\\' || *str == '\"') *p++ = '\\'; *p++ = *str++; } *p = '\0'; return ret; } /* * Valid JSON strings must escape '"' and '/' with a preceeding '/' */ static struct json_value *json_create_value_string(const char *str) { struct json_value *value = malloc(sizeof(struct json_value)); if (value) { value->type = JSON_TYPE_STRING; value->string = strdup_escape(str); if (!value->string) { free(value); value = NULL; } } return value; } static struct json_value *json_create_value_object(struct json_object *obj) { struct json_value *value = malloc(sizeof(struct json_value)); if (value) { value->type = JSON_TYPE_OBJECT; value->object = obj; obj->parent = value; } return value; } static struct json_value *json_create_value_array(struct json_array *array) { struct json_value *value = malloc(sizeof(struct json_value)); if (value) { value->type = JSON_TYPE_ARRAY; value->array = array; array->parent = value; } return value; } static void json_free_pair(struct json_pair *pair); static void json_free_value(struct json_value *value); void json_free_object(struct json_object *obj) { int i; for (i = 0; i < obj->pair_cnt; i++) json_free_pair(obj->pairs[i]); free(obj->pairs); free(obj); } static void json_free_array(struct json_array *array) { int i; for (i = 0; i < array->value_cnt; i++) json_free_value(array->values[i]); free(array->values); free(array); } static void json_free_pair(struct json_pair *pair) { json_free_value(pair->value); free(pair->name); free(pair); } static void json_free_value(struct json_value *value) { switch (value->type) { case JSON_TYPE_STRING: free(value->string); break; case JSON_TYPE_OBJECT: json_free_object(value->object); break; case JSON_TYPE_ARRAY: json_free_array(value->array); break; } free(value); } static int json_array_add_value(struct json_array *array, struct json_value *value) { struct json_value **values = realloc(array->values, sizeof(struct json_value *) * (array->value_cnt + 1)); if (!values) return ENOMEM; values[array->value_cnt] = value; array->value_cnt++; array->values = values; value->parent_type = JSON_PARENT_TYPE_ARRAY; value->parent_array = array; return 0; } static int json_object_add_pair(struct json_object *obj, struct json_pair *pair) { struct json_pair **pairs = realloc(obj->pairs, sizeof(struct json_pair *) * (obj->pair_cnt + 1)); if (!pairs) return ENOMEM; pairs[obj->pair_cnt] = pair; obj->pair_cnt++; obj->pairs = pairs; pair->parent = obj; return 0; } int json_object_add_value_type(struct json_object *obj, const char *name, int type, ...) { struct json_value *value; struct json_pair *pair; va_list args; int ret; va_start(args, type); if (type == JSON_TYPE_STRING) value = json_create_value_string(va_arg(args, char *)); else if (type == JSON_TYPE_INTEGER) value = json_create_value_int(va_arg(args, long)); else if (type == JSON_TYPE_FLOAT) value = json_create_value_float(va_arg(args, double)); else if (type == JSON_TYPE_OBJECT) value = json_create_value_object(va_arg(args, struct json_object *)); else value = json_create_value_array(va_arg(args, struct json_array *)); va_end(args); if (!value) return ENOMEM; pair = json_create_pair(name, value); if (!pair) { json_free_value(value); return ENOMEM; } ret = json_object_add_pair(obj, pair); if (ret) { json_free_pair(pair); return ENOMEM; } return 0; } static void json_print_array(struct json_array *array); int json_array_add_value_type(struct json_array *array, int type, ...) { struct json_value *value; va_list args; int ret; va_start(args, type); if (type == JSON_TYPE_STRING) value = json_create_value_string(va_arg(args, char *)); else if (type == JSON_TYPE_INTEGER) value = json_create_value_int(va_arg(args, long)); else if (type == JSON_TYPE_FLOAT) value = json_create_value_float(va_arg(args, double)); else if (type == JSON_TYPE_OBJECT) value = json_create_value_object(va_arg(args, struct json_object *)); else value = json_create_value_array(va_arg(args, struct json_array *)); va_end(args); if (!value) return ENOMEM; ret = json_array_add_value(array, value); if (ret) { json_free_value(value); return ENOMEM; } return 0; } static int json_value_level(struct json_value *value); static int json_pair_level(struct json_pair *pair); static int json_array_level(struct json_array *array); static int json_object_level(struct json_object *object) { if (object->parent == NULL) return 0; return json_value_level(object->parent); } static int json_pair_level(struct json_pair *pair) { return json_object_level(pair->parent) + 1; } static int json_array_level(struct json_array *array) { return json_value_level(array->parent); } static int json_value_level(struct json_value *value) { if (value->parent_type == JSON_PARENT_TYPE_PAIR) return json_pair_level(value->parent_pair); else return json_array_level(value->parent_array) + 1; } static void json_print_level(int level) { while (level-- > 0) log_info(" "); } static void json_print_pair(struct json_pair *pair); static void json_print_array(struct json_array *array); static void json_print_value(struct json_value *value); void json_print_object(struct json_object *obj) { int i; log_info("{\n"); for (i = 0; i < obj->pair_cnt; i++) { if (i > 0) log_info(",\n"); json_print_pair(obj->pairs[i]); } log_info("\n"); json_print_level(json_object_level(obj)); log_info("}"); } static void json_print_pair(struct json_pair *pair) { json_print_level(json_pair_level(pair)); log_info("\"%s\" : ", pair->name); json_print_value(pair->value); } static void json_print_array(struct json_array *array) { int i; log_info("[\n"); for (i = 0; i < array->value_cnt; i++) { if (i > 0) log_info(",\n"); json_print_level(json_value_level(array->values[i])); json_print_value(array->values[i]); } log_info("\n"); json_print_level(json_array_level(array)); log_info("]"); } static void json_print_value(struct json_value *value) { switch (value->type) { case JSON_TYPE_STRING: log_info("\"%s\"", value->string); break; case JSON_TYPE_INTEGER: log_info("%ld", value->integer_number); break; case JSON_TYPE_FLOAT: log_info("%.2f", value->float_number); break; case JSON_TYPE_OBJECT: json_print_object(value->object); break; case JSON_TYPE_ARRAY: json_print_array(value->array); break; } } fio-2.1.3/json.h000066400000000000000000000044331222032232000134040ustar00rootroot00000000000000#ifndef __JSON__H #define __JSON__H struct json_object; struct json_array; struct json_pair; #define JSON_TYPE_STRING 0 #define JSON_TYPE_INTEGER 1 #define JSON_TYPE_FLOAT 2 #define JSON_TYPE_OBJECT 3 #define JSON_TYPE_ARRAY 4 #define JSON_PARENT_TYPE_PAIR 0 #define JSON_PARENT_TYPE_ARRAY 1 struct json_value { int type; union { long integer_number; double float_number; char *string; struct json_object *object; struct json_array *array; }; int parent_type; union { struct json_pair *parent_pair; struct json_array *parent_array; }; }; struct json_array { struct json_value **values; int value_cnt; struct json_value *parent; }; struct json_object { struct json_pair **pairs; int pair_cnt; struct json_value *parent; }; struct json_pair { char *name; struct json_value *value; struct json_object *parent; }; struct json_object *json_create_object(void); struct json_array *json_create_array(void); void json_free_object(struct json_object *obj); int json_object_add_value_type(struct json_object *obj, const char *name, int type, ...); #define json_object_add_value_int(obj, name, val) \ json_object_add_value_type((obj), name, JSON_TYPE_INTEGER, (val)) #define json_object_add_value_float(obj, name, val) \ json_object_add_value_type((obj), name, JSON_TYPE_FLOAT, (val)) #define json_object_add_value_string(obj, name, val) \ json_object_add_value_type((obj), name, JSON_TYPE_STRING, (val)) #define json_object_add_value_object(obj, name, val) \ json_object_add_value_type((obj), name, JSON_TYPE_OBJECT, (val)) #define json_object_add_value_array(obj, name, val) \ json_object_add_value_type((obj), name, JSON_TYPE_ARRAY, (val)) int json_array_add_value_type(struct json_array *array, int type, ...); #define json_array_add_value_int(obj, val) \ json_array_add_value_type((obj), JSON_TYPE_INTEGER, (val)) #define json_array_add_value_float(obj, val) \ json_array_add_value_type((obj), JSON_TYPE_FLOAT, (val)) #define json_array_add_value_string(obj, val) \ json_array_add_value_type((obj), JSON_TYPE_STRING, (val)) #define json_array_add_value_object(obj, val) \ json_array_add_value_type((obj), JSON_TYPE_OBJECT, (val)) #define json_array_add_value_array(obj, val) \ json_array_add_value_type((obj), JSON_TYPE_ARRAY, (val)) void json_print_object(struct json_object *obj); #endif fio-2.1.3/lib/000077500000000000000000000000001222032232000130245ustar00rootroot00000000000000fio-2.1.3/lib/axmap.c000066400000000000000000000246441222032232000143100ustar00rootroot00000000000000/* * Bitmap of bitmaps, where each layer is number-of-bits-per-word smaller than * the previous. Hence an 'axmap', since we axe each previous layer into a * much smaller piece. I swear, that is why it's named like that. It has * nothing to do with anything remotely narcissistic. * * A set bit at layer N indicates a full word at layer N-1, and so forth. As * the bitmap becomes progressively more full, checking for existance * becomes cheaper (since fewer layers are walked, making it a lot more * cache friendly) and locating the next free space likewise. * * Axmaps get pretty close to optimal (1 bit per block) space usage, since * layers quickly diminish in size. Doing the size math is straight forward, * since we have log64(blocks) layers of maps. For 20000 blocks, overhead * is roughly 1.9%, or 1.019 bits per block. The number quickly converges * towards 1.0158, or 1.58% of overhead. */ #include #include #include #include #include "../arch/arch.h" #include "axmap.h" #include "../smalloc.h" #include "../minmax.h" #if BITS_PER_LONG == 64 #define UNIT_SHIFT 6 #elif BITS_PER_LONG == 32 #define UNIT_SHIFT 5 #else #error "Number of arch bits unknown" #endif #define BLOCKS_PER_UNIT (1UL << UNIT_SHIFT) #define BLOCKS_PER_UNIT_MASK (BLOCKS_PER_UNIT - 1) #define firstfree_valid(b) ((b)->first_free != (uint64_t) -1) struct axmap_level { int level; unsigned long map_size; unsigned long *map; }; struct axmap { unsigned int nr_levels; struct axmap_level *levels; uint64_t first_free; uint64_t nr_bits; }; static unsigned long ulog64(unsigned long val, unsigned int log) { while (log-- && val) val >>= UNIT_SHIFT; return val; } void axmap_reset(struct axmap *axmap) { int i; for (i = 0; i < axmap->nr_levels; i++) { struct axmap_level *al = &axmap->levels[i]; memset(al->map, 0, al->map_size * sizeof(unsigned long)); } axmap->first_free = 0; } void axmap_free(struct axmap *axmap) { unsigned int i; if (!axmap) return; for (i = 0; i < axmap->nr_levels; i++) sfree(axmap->levels[i].map); sfree(axmap->levels); sfree(axmap); } struct axmap *axmap_new(unsigned long nr_bits) { struct axmap *axmap; unsigned int i, levels; axmap = smalloc(sizeof(*axmap)); if (!axmap) return NULL; levels = 1; i = (nr_bits + BLOCKS_PER_UNIT - 1) >> UNIT_SHIFT; while (i > 1) { i = (i + BLOCKS_PER_UNIT - 1) >> UNIT_SHIFT; levels++; } axmap->nr_levels = levels; axmap->levels = smalloc(axmap->nr_levels * sizeof(struct axmap_level)); axmap->nr_bits = nr_bits; for (i = 0; i < axmap->nr_levels; i++) { struct axmap_level *al = &axmap->levels[i]; al->level = i; al->map_size = (nr_bits + BLOCKS_PER_UNIT - 1) >> UNIT_SHIFT; al->map = smalloc(al->map_size * sizeof(unsigned long)); if (!al->map) goto err; nr_bits = (nr_bits + BLOCKS_PER_UNIT - 1) >> UNIT_SHIFT; } axmap_reset(axmap); return axmap; err: for (i = 0; i < axmap->nr_levels; i++) if (axmap->levels[i].map) sfree(axmap->levels[i].map); sfree(axmap->levels); return NULL; } static int axmap_handler(struct axmap *axmap, uint64_t bit_nr, int (*func)(struct axmap_level *, unsigned long, unsigned int, void *), void *data) { struct axmap_level *al; int i; for (i = 0; i < axmap->nr_levels; i++) { unsigned long index = ulog64(bit_nr, i); unsigned long offset = index >> UNIT_SHIFT; unsigned int bit = index & BLOCKS_PER_UNIT_MASK; al = &axmap->levels[i]; if (func(al, offset, bit, data)) return 1; } return 0; } static int axmap_handler_topdown(struct axmap *axmap, uint64_t bit_nr, int (*func)(struct axmap_level *, unsigned long, unsigned int, void *), void *data) { struct axmap_level *al; int i, level = axmap->nr_levels; for (i = axmap->nr_levels - 1; i >= 0; i--) { unsigned long index = ulog64(bit_nr, --level); unsigned long offset = index >> UNIT_SHIFT; unsigned int bit = index & BLOCKS_PER_UNIT_MASK; al = &axmap->levels[i]; if (func(al, offset, bit, data)) return 1; } return 0; } static int axmap_clear_fn(struct axmap_level *al, unsigned long offset, unsigned int bit, void *unused) { if (!(al->map[offset] & (1UL << bit))) return 1; al->map[offset] &= ~(1UL << bit); return 0; } void axmap_clear(struct axmap *axmap, uint64_t bit_nr) { axmap_handler(axmap, bit_nr, axmap_clear_fn, NULL); } struct axmap_set_data { unsigned int nr_bits; unsigned int set_bits; }; static unsigned long bit_masks[] = { 0x0000000000000000, 0x0000000000000001, 0x0000000000000003, 0x0000000000000007, 0x000000000000000f, 0x000000000000001f, 0x000000000000003f, 0x000000000000007f, 0x00000000000000ff, 0x00000000000001ff, 0x00000000000003ff, 0x00000000000007ff, 0x0000000000000fff, 0x0000000000001fff, 0x0000000000003fff, 0x0000000000007fff, 0x000000000000ffff, 0x000000000001ffff, 0x000000000003ffff, 0x000000000007ffff, 0x00000000000fffff, 0x00000000001fffff, 0x00000000003fffff, 0x00000000007fffff, 0x0000000000ffffff, 0x0000000001ffffff, 0x0000000003ffffff, 0x0000000007ffffff, 0x000000000fffffff, 0x000000001fffffff, 0x000000003fffffff, 0x000000007fffffff, 0x00000000ffffffff, #if BITS_PER_LONG == 64 0x00000001ffffffff, 0x00000003ffffffff, 0x00000007ffffffff, 0x0000000fffffffff, 0x0000001fffffffff, 0x0000003fffffffff, 0x0000007fffffffff, 0x000000ffffffffff, 0x000001ffffffffff, 0x000003ffffffffff, 0x000007ffffffffff, 0x00000fffffffffff, 0x00001fffffffffff, 0x00003fffffffffff, 0x00007fffffffffff, 0x0000ffffffffffff, 0x0001ffffffffffff, 0x0003ffffffffffff, 0x0007ffffffffffff, 0x000fffffffffffff, 0x001fffffffffffff, 0x003fffffffffffff, 0x007fffffffffffff, 0x00ffffffffffffff, 0x01ffffffffffffff, 0x03ffffffffffffff, 0x07ffffffffffffff, 0x0fffffffffffffff, 0x1fffffffffffffff, 0x3fffffffffffffff, 0x7fffffffffffffff, 0xffffffffffffffff #endif }; static int axmap_set_fn(struct axmap_level *al, unsigned long offset, unsigned int bit, void *__data) { struct axmap_set_data *data = __data; unsigned long mask, overlap; unsigned int nr_bits; nr_bits = min(data->nr_bits, BLOCKS_PER_UNIT - bit); mask = bit_masks[nr_bits] << bit; /* * Mask off any potential overlap, only sets contig regions */ overlap = al->map[offset] & mask; if (overlap == mask) return 1; while (overlap) { unsigned long clear_mask = ~(1UL << ffz(~overlap)); mask &= clear_mask; overlap &= clear_mask; nr_bits--; } assert(mask); assert(!(al->map[offset] & mask)); al->map[offset] |= mask; if (!al->level) data->set_bits = nr_bits; data->nr_bits = 1; return al->map[offset] != -1UL; } static void __axmap_set(struct axmap *axmap, uint64_t bit_nr, struct axmap_set_data *data) { unsigned int set_bits, nr_bits = data->nr_bits; if (axmap->first_free >= bit_nr && axmap->first_free < bit_nr + data->nr_bits) axmap->first_free = -1ULL; if (bit_nr > axmap->nr_bits) return; else if (bit_nr + nr_bits > axmap->nr_bits) nr_bits = axmap->nr_bits - bit_nr; set_bits = 0; while (nr_bits) { axmap_handler(axmap, bit_nr, axmap_set_fn, data); set_bits += data->set_bits; if (!data->set_bits || data->set_bits != (BLOCKS_PER_UNIT - nr_bits)) break; nr_bits -= data->set_bits; bit_nr += data->set_bits; data->nr_bits = nr_bits; } data->set_bits = set_bits; } void axmap_set(struct axmap *axmap, uint64_t bit_nr) { struct axmap_set_data data = { .nr_bits = 1, }; __axmap_set(axmap, bit_nr, &data); } unsigned int axmap_set_nr(struct axmap *axmap, uint64_t bit_nr, unsigned int nr_bits) { unsigned int set_bits = 0; do { struct axmap_set_data data = { .nr_bits = nr_bits, }; unsigned int max_bits, this_set; max_bits = BLOCKS_PER_UNIT - (bit_nr & BLOCKS_PER_UNIT_MASK); if (max_bits < nr_bits) data.nr_bits = max_bits; this_set = data.nr_bits; __axmap_set(axmap, bit_nr, &data); set_bits += data.set_bits; if (data.set_bits != this_set) break; nr_bits -= data.set_bits; bit_nr += data.set_bits; } while (nr_bits); return set_bits; } static int axmap_isset_fn(struct axmap_level *al, unsigned long offset, unsigned int bit, void *unused) { return (al->map[offset] & (1UL << bit)) != 0; } int axmap_isset(struct axmap *axmap, uint64_t bit_nr) { if (bit_nr <= axmap->nr_bits) return axmap_handler_topdown(axmap, bit_nr, axmap_isset_fn, NULL); return 0; } static uint64_t axmap_find_first_free(struct axmap *axmap, unsigned int level, uint64_t index) { uint64_t ret = -1ULL; unsigned long j; int i; /* * Start at the bottom, then converge towards first free bit at the top */ for (i = level; i >= 0; i--) { struct axmap_level *al = &axmap->levels[i]; /* * Clear 'ret', this is a bug condition. */ if (index >= al->map_size) { ret = -1ULL; break; } for (j = index; j < al->map_size; j++) { if (al->map[j] == -1UL) continue; /* * First free bit here is our index into the first * free bit at the next higher level */ ret = index = (j << UNIT_SHIFT) + ffz(al->map[j]); break; } } if (ret < axmap->nr_bits) return ret; return (uint64_t) -1ULL; } uint64_t axmap_first_free(struct axmap *axmap) { if (firstfree_valid(axmap)) return axmap->first_free; axmap->first_free = axmap_find_first_free(axmap, axmap->nr_levels - 1, 0); return axmap->first_free; } struct axmap_next_free_data { unsigned int level; unsigned long offset; uint64_t bit; }; static int axmap_next_free_fn(struct axmap_level *al, unsigned long offset, unsigned int bit, void *__data) { struct axmap_next_free_data *data = __data; uint64_t mask = ~bit_masks[(data->bit + 1) & BLOCKS_PER_UNIT_MASK]; if (!(mask & ~al->map[offset])) return 0; if (al->map[offset] != -1UL) { data->level = al->level; data->offset = offset; return 1; } data->bit = (data->bit + BLOCKS_PER_UNIT - 1) / BLOCKS_PER_UNIT; return 0; } /* * 'bit_nr' is already set. Find the next free bit after this one. */ uint64_t axmap_next_free(struct axmap *axmap, uint64_t bit_nr) { struct axmap_next_free_data data = { .level = -1U, .bit = bit_nr, }; uint64_t ret; if (firstfree_valid(axmap) && bit_nr < axmap->first_free) return axmap->first_free; if (!axmap_handler(axmap, bit_nr, axmap_next_free_fn, &data)) return axmap_first_free(axmap); assert(data.level != -1U); /* * In the rare case that the map is unaligned, we might end up * finding an offset that's beyond the valid end. For that case, * find the first free one, the map is practically full. */ ret = axmap_find_first_free(axmap, data.level, data.offset); if (ret != -1ULL) return ret; return axmap_first_free(axmap); } fio-2.1.3/lib/axmap.h000066400000000000000000000010771222032232000143100ustar00rootroot00000000000000#ifndef FIO_BITMAP_H #define FIO_BITMAP_H #include struct axmap; struct axmap *axmap_new(unsigned long nr_bits); void axmap_free(struct axmap *bm); void axmap_clear(struct axmap *axmap, uint64_t bit_nr); void axmap_set(struct axmap *axmap, uint64_t bit_nr); unsigned int axmap_set_nr(struct axmap *axmap, uint64_t bit_nr, unsigned int nr_bits); int axmap_isset(struct axmap *axmap, uint64_t bit_nr); uint64_t axmap_first_free(struct axmap *axmap); uint64_t axmap_next_free(struct axmap *axmap, uint64_t bit_nr); void axmap_reset(struct axmap *axmap); #endif fio-2.1.3/lib/bswap.h000066400000000000000000000015331222032232000143130ustar00rootroot00000000000000#ifndef FIO_BSWAP_H #define FIO_BSWAP_H #include #ifdef CONFIG_LITTLE_ENDIAN static inline uint32_t __be32_to_cpu(uint32_t val) { uint32_t c1, c2, c3, c4; c1 = (val >> 24) & 0xff; c2 = (val >> 16) & 0xff; c3 = (val >> 8) & 0xff; c4 = val & 0xff; return c1 | c2 << 8 | c3 << 16 | c4 << 24; } static inline uint64_t __be64_to_cpu(uint64_t val) { uint64_t c1, c2, c3, c4, c5, c6, c7, c8; c1 = (val >> 56) & 0xff; c2 = (val >> 48) & 0xff; c3 = (val >> 40) & 0xff; c4 = (val >> 32) & 0xff; c5 = (val >> 24) & 0xff; c6 = (val >> 16) & 0xff; c7 = (val >> 8) & 0xff; c8 = val & 0xff; return c1 | c2 << 8 | c3 << 16 | c4 << 24 | c5 << 32 | c6 << 40 | c7 << 48 | c8 << 56; } #else static inline uint64_t __be64_to_cpu(uint64_t val) { return val; } static inline uint32_t __be32_to_cpu(uint32_t val) { return val; } #endif #endif fio-2.1.3/lib/ffz.h000066400000000000000000000010251222032232000137600ustar00rootroot00000000000000#ifndef FIO_FFZ_H #define FIO_FFZ_H static inline int __ffs(unsigned long word) { int r = 0; #if BITS_PER_LONG == 64 if ((word & 0xffffffff) == 0) { r += 32; word >>= 32; } #endif if (!(word & 0xffff)) { word >>= 16; r += 16; } if (!(word & 0xff)) { word >>= 8; r += 8; } if (!(word & 0xf)) { word >>= 4; r += 4; } if (!(word & 3)) { word >>= 2; r += 2; } if (!(word & 1)) { word >>= 1; r += 1; } return r; } static inline int ffz(unsigned long bitmask) { return __ffs(~bitmask); } #endif fio-2.1.3/lib/flist_sort.c000066400000000000000000000066501222032232000153670ustar00rootroot00000000000000#include #include #include "../flist.h" #include "../log.h" #define MAX_LIST_LENGTH_BITS 20 /* * Returns a list organized in an intermediate format suited * to chaining of merge() calls: null-terminated, no reserved or * sentinel head node, "prev" links not maintained. */ static struct flist_head *merge(void *priv, int (*cmp)(void *priv, struct flist_head *a, struct flist_head *b), struct flist_head *a, struct flist_head *b) { struct flist_head head, *tail = &head; while (a && b) { /* if equal, take 'a' -- important for sort stability */ if ((*cmp)(priv, a, b) <= 0) { tail->next = a; a = a->next; } else { tail->next = b; b = b->next; } tail = tail->next; } tail->next = a?:b; return head.next; } /* * Combine final list merge with restoration of standard doubly-linked * list structure. This approach duplicates code from merge(), but * runs faster than the tidier alternatives of either a separate final * prev-link restoration pass, or maintaining the prev links * throughout. */ static void merge_and_restore_back_links(void *priv, int (*cmp)(void *priv, struct flist_head *a, struct flist_head *b), struct flist_head *head, struct flist_head *a, struct flist_head *b) { struct flist_head *tail = head; while (a && b) { /* if equal, take 'a' -- important for sort stability */ if ((*cmp)(priv, a, b) <= 0) { tail->next = a; a->prev = tail; a = a->next; } else { tail->next = b; b->prev = tail; b = b->next; } tail = tail->next; } tail->next = a ? : b; do { /* * In worst cases this loop may run many iterations. * Continue callbacks to the client even though no * element comparison is needed, so the client's cmp() * routine can invoke cond_resched() periodically. */ (*cmp)(priv, tail->next, tail->next); tail->next->prev = tail; tail = tail->next; } while (tail->next); tail->next = head; head->prev = tail; } /** * list_sort - sort a list * @priv: private data, opaque to list_sort(), passed to @cmp * @head: the list to sort * @cmp: the elements comparison function * * This function implements "merge sort", which has O(nlog(n)) * complexity. * * The comparison function @cmp must return a negative value if @a * should sort before @b, and a positive value if @a should sort after * @b. If @a and @b are equivalent, and their original relative * ordering is to be preserved, @cmp must return 0. */ void flist_sort(void *priv, struct flist_head *head, int (*cmp)(void *priv, struct flist_head *a, struct flist_head *b)) { struct flist_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists -- last slot is a sentinel */ int lev; /* index into part[] */ int max_lev = 0; struct flist_head *list; if (flist_empty(head)) return; memset(part, 0, sizeof(part)); head->prev->next = NULL; list = head->next; while (list) { struct flist_head *cur = list; list = list->next; cur->next = NULL; for (lev = 0; part[lev]; lev++) { cur = merge(priv, cmp, part[lev], cur); part[lev] = NULL; } if (lev > max_lev) { if (lev >= MAX_LIST_LENGTH_BITS) { log_err("fio: list passed to" " list_sort() too long for" " efficiency\n"); lev--; } max_lev = lev; } part[lev] = cur; } for (lev = 0; lev < max_lev; lev++) if (part[lev]) list = merge(priv, cmp, part[lev], list); merge_and_restore_back_links(priv, cmp, head, part[max_lev], list); } fio-2.1.3/lib/fls.h000066400000000000000000000011641222032232000137630ustar00rootroot00000000000000#ifndef _ASM_GENERIC_BITOPS_FLS_H_ #define _ASM_GENERIC_BITOPS_FLS_H_ /** * fls - find last (most-significant) bit set * @x: the word to search * * This is defined the same way as ffs. * Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32. */ static inline int __fls(int x) { int r = 32; if (!x) return 0; if (!(x & 0xffff0000u)) { x <<= 16; r -= 16; } if (!(x & 0xff000000u)) { x <<= 8; r -= 8; } if (!(x & 0xf0000000u)) { x <<= 4; r -= 4; } if (!(x & 0xc0000000u)) { x <<= 2; r -= 2; } if (!(x & 0x80000000u)) { x <<= 1; r -= 1; } return r; } #endif /* _ASM_GENERIC_BITOPS_FLS_H_ */ fio-2.1.3/lib/getopt.h000066400000000000000000000005401222032232000144760ustar00rootroot00000000000000#ifdef CONFIG_GETOPT_LONG_ONLY #include #else #ifndef _GETOPT_H #define _GETOPT_H struct option { const char *name; int has_arg; int *flag; int val; }; enum { no_argument = 0, required_argument = 1, optional_argument = 2, }; int getopt_long_only(int, char *const *, const char *, const struct option *, int *); #endif #endif fio-2.1.3/lib/getopt_long.c000066400000000000000000000067041222032232000155200ustar00rootroot00000000000000/* * getopt.c * * getopt_long(), or at least a common subset thereof: * * - Option reordering is not supported * - -W foo is not supported * - First optstring character "-" not supported. * * This file was imported from the klibc library from hpa */ #include #include #include #include "getopt.h" char *optarg = NULL; int optind = 0, opterr = 0, optopt = 0; static struct getopt_private_state { const char *optptr; const char *last_optstring; char *const *last_argv; } pvt; static inline const char *option_matches(const char *arg_str, const char *opt_name) { while (*arg_str != '\0' && *arg_str != '=') { if (*arg_str++ != *opt_name++) return NULL; } if (*opt_name) return NULL; return arg_str; } int getopt_long_only(int argc, char *const *argv, const char *optstring, const struct option *longopts, int *longindex) { const char *carg; const char *osptr; int opt; optarg = NULL; /* getopt() relies on a number of different global state variables, which can make this really confusing if there is more than one use of getopt() in the same program. This attempts to detect that situation by detecting if the "optstring" or "argv" argument have changed since last time we were called; if so, reinitialize the query state. */ if (optstring != pvt.last_optstring || argv != pvt.last_argv || optind < 1 || optind > argc) { /* optind doesn't match the current query */ pvt.last_optstring = optstring; pvt.last_argv = argv; optind = 1; pvt.optptr = NULL; } carg = argv[optind]; /* First, eliminate all non-option cases */ if (!carg || carg[0] != '-' || !carg[1]) return -1; if (carg[1] == '-') { const struct option *lo; const char *opt_end = NULL; optind++; /* Either it's a long option, or it's -- */ if (!carg[2]) { /* It's -- */ return -1; } for (lo = longopts; lo->name; lo++) { if ((opt_end = option_matches(carg+2, lo->name))) break; } if (!opt_end) return '?'; if (longindex) *longindex = lo-longopts; if (*opt_end == '=') { if (lo->has_arg) optarg = (char *)opt_end+1; else return '?'; } else if (lo->has_arg == 1) { if (!(optarg = argv[optind])) return '?'; optind++; } if (lo->flag) { *lo->flag = lo->val; return 0; } else { return lo->val; } } if ((uintptr_t) (pvt.optptr - carg) > (uintptr_t) strlen(carg)) { /* Someone frobbed optind, change to new opt. */ pvt.optptr = carg + 1; } opt = *pvt.optptr++; if (opt != ':' && (osptr = strchr(optstring, opt))) { if (osptr[1] == ':') { if (*pvt.optptr) { /* Argument-taking option with attached argument */ optarg = (char *)pvt.optptr; optind++; } else { /* Argument-taking option with non-attached argument */ if (osptr[2] == ':') { if (argv[optind + 1]) { optarg = (char *)argv[optind+1]; optind += 2; } else { optarg = NULL; optind++; } return opt; } else if (argv[optind + 1]) { optarg = (char *)argv[optind+1]; optind += 2; } else { /* Missing argument */ optind++; return (optstring[0] == ':') ? ':' : '?'; } } return opt; } else { /* Non-argument-taking option */ /* pvt.optptr will remember the exact position to resume at */ if (!*pvt.optptr) optind++; return opt; } } else { /* Unknown option */ optopt = opt; if (!*pvt.optptr) optind++; return '?'; } } fio-2.1.3/lib/getrusage.c000066400000000000000000000004211222032232000151530ustar00rootroot00000000000000#include #include "getrusage.h" int fio_getrusage(struct rusage *ru) { #ifdef CONFIG_RUSAGE_THREAD if (!getrusage(RUSAGE_THREAD, ru)) return 0; if (errno != EINVAL) return -1; /* Fall through to RUSAGE_SELF */ #endif return getrusage(RUSAGE_SELF, ru); } fio-2.1.3/lib/getrusage.h000066400000000000000000000002271222032232000151640ustar00rootroot00000000000000#ifndef FIO_GETRUSAGE_H #define FIO_GETRUSAGE_H #include #include extern int fio_getrusage(struct rusage *ru); #endif fio-2.1.3/lib/hweight.c000066400000000000000000000015471222032232000146360ustar00rootroot00000000000000#include "hweight.h" unsigned int hweight8(uint8_t w) { unsigned int res = w - ((w >> 1) & 0x55); res = (res & 0x33) + ((res >> 2) & 0x33); return (res + (res >> 4)) & 0x0F; } unsigned int hweight32(uint32_t w) { unsigned int res = w - ((w >> 1) & 0x55555555); res = (res & 0x33333333) + ((res >> 2) & 0x33333333); res = (res + (res >> 4)) & 0x0F0F0F0F; res = res + (res >> 8); return (res + (res >> 16)) & 0x000000FF; } unsigned int hweight64(uint64_t w) { #if BITS_PER_LONG == 32 return hweight32((unsigned int)(w >> 32)) + hweight32((unsigned int)w); #else uint64_t res = w - ((w >> 1) & 0x5555555555555555ULL); res = (res & 0x3333333333333333ULL) + ((res >> 2) & 0x3333333333333333ULL); res = (res + (res >> 4)) & 0x0F0F0F0F0F0F0F0FULL; res = res + (res >> 8); res = res + (res >> 16); return (res + (res >> 32)) & 0x00000000000000FFULL; #endif } fio-2.1.3/lib/hweight.h000066400000000000000000000002661222032232000146400ustar00rootroot00000000000000#ifndef FIO_HWEIGHT_H #define FIO_HWEIGHT_H #include unsigned int hweight8(uint8_t w); unsigned int hweight32(uint32_t w); unsigned int hweight64(uint64_t w); #endif fio-2.1.3/lib/ieee754.c000066400000000000000000000035271222032232000143460ustar00rootroot00000000000000/* * Shamelessly lifted from Beej's Guide to Network Programming, found here: * * http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html#serialization * * Below code was granted to the public domain. */ #include #include "ieee754.h" uint64_t pack754(long double f, unsigned bits, unsigned expbits) { long double fnorm; int shift; long long sign, exp, significand; unsigned significandbits = bits - expbits - 1; // -1 for sign bit // get this special case out of the way if (f == 0.0) return 0; // check sign and begin normalization if (f < 0) { sign = 1; fnorm = -f; } else { sign = 0; fnorm = f; } // get the normalized form of f and track the exponent shift = 0; while (fnorm >= 2.0) { fnorm /= 2.0; shift++; } while (fnorm < 1.0) { fnorm *= 2.0; shift--; } fnorm = fnorm - 1.0; // calculate the binary form (non-float) of the significand data significand = fnorm * ((1LL << significandbits) + 0.5f); // get the biased exponent exp = shift + ((1 << (expbits - 1)) - 1); // shift + bias // return the final answer return (sign << (bits - 1)) | (exp << (bits-expbits - 1)) | significand; } long double unpack754(uint64_t i, unsigned bits, unsigned expbits) { long double result; long long shift; unsigned bias; unsigned significandbits = bits - expbits - 1; // -1 for sign bit if (i == 0) return 0.0; // pull the significand result = (i & ((1LL << significandbits) - 1)); // mask result /= (1LL << significandbits); // convert back to float result += 1.0f; // add the one back on // deal with the exponent bias = (1 << (expbits - 1)) - 1; shift = ((i >> significandbits) & ((1LL << expbits) - 1)) - bias; while (shift > 0) { result *= 2.0; shift--; } while (shift < 0) { result /= 2.0; shift++; } // sign it result *= (i >> (bits - 1)) & 1 ? -1.0 : 1.0; return result; } fio-2.1.3/lib/ieee754.h000066400000000000000000000006751222032232000143540ustar00rootroot00000000000000#ifndef FIO_IEEE754_H #define FIO_IEEE754_H #include extern uint64_t pack754(long double f, unsigned bits, unsigned expbits); extern long double unpack754(uint64_t i, unsigned bits, unsigned expbits); #define fio_double_to_uint64(val) pack754((val), 64, 11) #define fio_uint64_to_double(val) unpack754((val), 64, 11) typedef struct fio_fp64 { union { uint64_t i; double f; uint8_t filler[16]; } u; } fio_fp64_t; #endif fio-2.1.3/lib/inet_aton.c000066400000000000000000000001641222032232000151510ustar00rootroot00000000000000#include "inet_aton.h" int inet_aton(const char *cp, struct in_addr *inp) { return inet_pton(AF_INET, cp, inp); } fio-2.1.3/lib/inet_aton.h000066400000000000000000000002151222032232000151530ustar00rootroot00000000000000#ifndef FIO_INET_ATON_LIB_H #define FIO_INET_ATON_LIB_H #include int inet_aton(const char *cp, struct in_addr *inp); #endif fio-2.1.3/lib/lfsr.c000066400000000000000000000171731222032232000141470ustar00rootroot00000000000000#include #include #include "lfsr.h" /* * LFSR taps retrieved from: * http://home1.gte.net/res0658s/electronics/LFSRtaps.html * * The memory overhead of the following tap table should be relatively small, * no more than 400 bytes. */ static uint8_t taps[64][FIO_MAX_TAPS] = { {0}, {0}, {0}, //LFSRs with less that 3-bits cannot exist {3, 2}, //Tap position for 3-bit LFSR {4, 3}, //Tap position for 4-bit LFSR {5, 3}, //Tap position for 5-bit LFSR {6, 5}, //Tap position for 6-bit LFSR {7, 6}, //Tap position for 7-bit LFSR {8, 6, 5 ,4}, //Tap position for 8-bit LFSR {9, 5}, //Tap position for 9-bit LFSR {10, 7}, //Tap position for 10-bit LFSR {11, 9}, //Tap position for 11-bit LFSR {12, 6, 4, 1}, //Tap position for 12-bit LFSR {13, 4, 3, 1}, //Tap position for 13-bit LFSR {14, 5, 3, 1}, //Tap position for 14-bit LFSR {15, 14}, //Tap position for 15-bit LFSR {16, 15, 13, 4}, //Tap position for 16-bit LFSR {17, 14}, //Tap position for 17-bit LFSR {18, 11}, //Tap position for 18-bit LFSR {19, 6, 2, 1}, //Tap position for 19-bit LFSR {20, 17}, //Tap position for 20-bit LFSR {21, 19}, //Tap position for 21-bit LFSR {22, 21}, //Tap position for 22-bit LFSR {23, 18}, //Tap position for 23-bit LFSR {24, 23, 22, 17}, //Tap position for 24-bit LFSR {25, 22}, //Tap position for 25-bit LFSR {26, 6, 2, 1}, //Tap position for 26-bit LFSR {27, 5, 2, 1}, //Tap position for 27-bit LFSR {28, 25}, //Tap position for 28-bit LFSR {29, 27}, //Tap position for 29-bit LFSR {30, 6, 4, 1}, //Tap position for 30-bit LFSR {31, 28}, //Tap position for 31-bit LFSR {32, 31, 29, 1}, //Tap position for 32-bit LFSR {33, 20}, //Tap position for 33-bit LFSR {34, 27, 2, 1}, //Tap position for 34-bit LFSR {35, 33}, //Tap position for 35-bit LFSR {36, 25}, //Tap position for 36-bit LFSR {37, 5, 4, 3, 2, 1}, //Tap position for 37-bit LFSR {38, 6, 5, 1}, //Tap position for 38-bit LFSR {39, 35}, //Tap position for 39-bit LFSR {40, 38, 21, 19}, //Tap position for 40-bit LFSR {41, 38}, //Tap position for 41-bit LFSR {42, 41, 20, 19}, //Tap position for 42-bit LFSR {43, 42, 38, 37}, //Tap position for 43-bit LFSR {44, 43, 18, 17}, //Tap position for 44-bit LFSR {45, 44, 42, 41}, //Tap position for 45-bit LFSR {46, 45, 26, 25}, //Tap position for 46-bit LFSR {47, 42}, //Tap position for 47-bit LFSR {48, 47, 21, 20}, //Tap position for 48-bit LFSR {49, 40}, //Tap position for 49-bit LFSR {50, 49, 24, 23}, //Tap position for 50-bit LFSR {51, 50, 36, 35}, //Tap position for 51-bit LFSR {52, 49}, //Tap position for 52-bit LFSR {53, 52, 38, 37}, //Tap position for 53-bit LFSR {54, 53, 18, 17}, //Tap position for 54-bit LFSR {55, 31}, //Tap position for 55-bit LFSR {56, 55, 35, 34}, //Tap position for 56-bit LFSR {57, 50}, //Tap position for 57-bit LFSR {58, 39}, //Tap position for 58-bit LFSR {59, 58, 38, 37}, //Tap position for 59-bit LFSR {60, 59}, //Tap position for 60-bit LFSR {61, 60, 46, 45}, //Tap position for 61-bit LFSR {62, 61, 6, 5}, //Tap position for 62-bit LFSR {63, 62}, //Tap position for 63-bit LFSR }; #define __LFSR_NEXT(__fl, __v) \ __v = ((__v >> 1) | __fl->cached_bit) ^ \ (((__v & 1UL) - 1UL) & __fl->xormask); static inline void __lfsr_next(struct fio_lfsr *fl, unsigned int spin) { /* * This should be O(1) since most compilers will create a jump table for * this switch. */ switch (spin) { case 15: __LFSR_NEXT(fl, fl->last_val); case 14: __LFSR_NEXT(fl, fl->last_val); case 13: __LFSR_NEXT(fl, fl->last_val); case 12: __LFSR_NEXT(fl, fl->last_val); case 11: __LFSR_NEXT(fl, fl->last_val); case 10: __LFSR_NEXT(fl, fl->last_val); case 9: __LFSR_NEXT(fl, fl->last_val); case 8: __LFSR_NEXT(fl, fl->last_val); case 7: __LFSR_NEXT(fl, fl->last_val); case 6: __LFSR_NEXT(fl, fl->last_val); case 5: __LFSR_NEXT(fl, fl->last_val); case 4: __LFSR_NEXT(fl, fl->last_val); case 3: __LFSR_NEXT(fl, fl->last_val); case 2: __LFSR_NEXT(fl, fl->last_val); case 1: __LFSR_NEXT(fl, fl->last_val); case 0: __LFSR_NEXT(fl, fl->last_val); default: break; } } /* * lfsr_next does the following: * * a. Return if the number of max values has been exceeded. * b. Check if we have a spin value that produces a repeating subsequence. * This is previously calculated in `prepare_spin` and cycle_length should * be > 0. If we do have such a spin: * * i. Decrement the calculated cycle. * ii. If it reaches zero, add "+1" to the spin and reset the cycle_length * (we have it cached in the struct fio_lfsr) * * In either case, continue with the calculation of the next value. * c. Check if the calculated value exceeds the desirable range. In this case, * go back to b, else return. */ int lfsr_next(struct fio_lfsr *fl, uint64_t *off, uint64_t last) { if (fl->num_vals++ > fl->max_val) return 1; do { if (fl->cycle_length && !--fl->cycle_length) { __lfsr_next(fl, fl->spin + 1); fl->cycle_length = fl->cached_cycle_length; goto check; } __lfsr_next(fl, fl->spin); check: ; } while (fl->last_val > fl->max_val); *off = fl->last_val; return 0; } static uint64_t lfsr_create_xormask(uint8_t *taps) { int i; uint64_t xormask = 0; for(i = 0; i < FIO_MAX_TAPS && taps[i] != 0; i++) xormask |= 1UL << (taps[i] - 1); return xormask; } static uint8_t *find_lfsr(uint64_t size) { int i; /* * For an LFSR, there is always a prohibited state (all ones). * Thus, if we need to find the proper LFSR for our size, we must take that * into account. */ for (i = 3; i < 64; i++) if ((1UL << i) > size) return taps[i]; return NULL; } /* * It is well-known that all maximal n-bit LFSRs will start repeating * themselves after their 2^n iteration. The introduction of spins however, is * possible to create a repetition of a sub-sequence before we hit that mark. * This happens if: * * [1]: ((2^n - 1) * i) % (spin + 1) == 0, * where "n" is LFSR's bits and "i" any number within the range [1,spin] * * It is important to know beforehand if a spin can cause a repetition of a * sub-sequence (cycle) and its length. However, calculating (2^n - 1) * i may * produce a buffer overflow for "n" close to 64, so we expand the above to: * * [2]: (2^n - 1) -> (x * (spin + 1) + y), where x >= 0 and 0 <= y <= spin * * Thus, [1] is equivalent to (y * i) % (spin + 1) == 0; * Also, the cycle's length will be (x * i) + (y * i) / (spin + 1) */ int prepare_spin(struct fio_lfsr *fl, unsigned int spin) { uint64_t max = (fl->cached_bit << 1) - 1; uint64_t x, y; int i; if (spin > 15) return 1; x = max / (spin + 1); y = max % (spin + 1); fl->cycle_length = 0; /* No cycle occurs, other than the expected */ fl->spin = spin; for (i = 1; i <= spin; i++) { if ((y * i) % (spin + 1) == 0) { fl->cycle_length = (x * i) + (y * i) / (spin + 1); break; } } fl->cached_cycle_length = fl->cycle_length; /* * Increment cycle length for the first time only since the stored value * will not be printed otherwise. */ fl->cycle_length++; return 0; } int lfsr_reset(struct fio_lfsr *fl, unsigned long seed) { uint64_t bitmask = (fl->cached_bit << 1) - 1; fl->num_vals = 0; fl->last_val = seed & bitmask; /* All-ones state is illegal for XNOR LFSRs */ if (fl->last_val == bitmask) return 1; return 0; } int lfsr_init(struct fio_lfsr *fl, uint64_t nums, unsigned long seed, unsigned int spin) { uint8_t *lfsr_taps; lfsr_taps = find_lfsr(nums); if (!lfsr_taps) return 1; fl->max_val = nums - 1; fl->xormask = lfsr_create_xormask(lfsr_taps); fl->cached_bit = 1UL << (lfsr_taps[0] - 1); if (prepare_spin(fl, spin)) return 1; if (lfsr_reset(fl, seed)) return 1; return 0; } fio-2.1.3/lib/lfsr.h000066400000000000000000000011051222032232000141400ustar00rootroot00000000000000#ifndef FIO_LFSR_H #define FIO_LFSR_H #include #define FIO_MAX_TAPS 6 struct lfsr_taps { unsigned int length; unsigned int taps[FIO_MAX_TAPS]; }; struct fio_lfsr { uint64_t xormask; uint64_t last_val; uint64_t cached_bit; uint64_t max_val; uint64_t num_vals; uint64_t cycle_length; uint64_t cached_cycle_length; unsigned int spin; }; int lfsr_next(struct fio_lfsr *fl, uint64_t *off, uint64_t); int lfsr_init(struct fio_lfsr *fl, uint64_t size, unsigned long seed, unsigned int spin); int lfsr_reset(struct fio_lfsr *fl, unsigned long seed); #endif fio-2.1.3/lib/num2str.c000066400000000000000000000026451222032232000146110ustar00rootroot00000000000000#include #include #include /* * Cheesy number->string conversion, complete with carry rounding error. */ char *num2str(unsigned long num, int maxlen, int base, int pow2, int unit_base) { const char *postfix[] = { "", "K", "M", "G", "P", "E" }; const char *byte_postfix[] = { "", "B", "bit" }; const unsigned int thousand[] = { 1000, 1024 }; unsigned int modulo, decimals; int byte_post_index = 0, post_index, carry = 0; char tmp[32]; char *buf; buf = malloc(128); for (post_index = 0; base > 1; post_index++) base /= thousand[!!pow2]; switch (unit_base) { case 1: byte_post_index = 2; num *= 8; break; case 8: byte_post_index = 1; break; } modulo = -1U; while (post_index < sizeof(postfix)) { sprintf(tmp, "%lu", num); if (strlen(tmp) <= maxlen) break; modulo = num % thousand[!!pow2]; num /= thousand[!!pow2]; carry = modulo >= thousand[!!pow2] / 2; post_index++; } if (modulo == -1U) { done: sprintf(buf, "%lu%s%s", num, postfix[post_index], byte_postfix[byte_post_index]); return buf; } sprintf(tmp, "%lu", num); decimals = maxlen - strlen(tmp); if (decimals <= 1) { if (carry) num++; goto done; } do { sprintf(tmp, "%u", modulo); if (strlen(tmp) <= decimals - 1) break; modulo = (modulo + 9) / 10; } while (1); sprintf(buf, "%lu.%u%s%s", num, modulo, postfix[post_index], byte_postfix[byte_post_index]); return buf; } fio-2.1.3/lib/prio_tree.c000066400000000000000000000272751222032232000151750ustar00rootroot00000000000000/* * lib/prio_tree.c - priority search tree * * Copyright (C) 2004, Rajesh Venkatasubramanian * * This file is released under the GPL v2. * * Based on the radix priority search tree proposed by Edward M. McCreight * SIAM Journal of Computing, vol. 14, no.2, pages 257-276, May 1985 * * 02Feb2004 Initial version */ #include #include #include "../fio.h" #include "prio_tree.h" /* * A clever mix of heap and radix trees forms a radix priority search tree (PST) * which is useful for storing intervals, e.g, we can consider a vma as a closed * interval of file pages [offset_begin, offset_end], and store all vmas that * map a file in a PST. Then, using the PST, we can answer a stabbing query, * i.e., selecting a set of stored intervals (vmas) that overlap with (map) a * given input interval X (a set of consecutive file pages), in "O(log n + m)" * time where 'log n' is the height of the PST, and 'm' is the number of stored * intervals (vmas) that overlap (map) with the input interval X (the set of * consecutive file pages). * * In our implementation, we store closed intervals of the form [radix_index, * heap_index]. We assume that always radix_index <= heap_index. McCreight's PST * is designed for storing intervals with unique radix indices, i.e., each * interval have different radix_index. However, this limitation can be easily * overcome by using the size, i.e., heap_index - radix_index, as part of the * index, so we index the tree using [(radix_index,size), heap_index]. * * When the above-mentioned indexing scheme is used, theoretically, in a 32 bit * machine, the maximum height of a PST can be 64. We can use a balanced version * of the priority search tree to optimize the tree height, but the balanced * tree proposed by McCreight is too complex and memory-hungry for our purpose. */ static void get_index(const struct prio_tree_node *node, unsigned long *radix, unsigned long *heap) { *radix = node->start; *heap = node->last; } static unsigned long index_bits_to_maxindex[BITS_PER_LONG]; void fio_init prio_tree_init(void) { unsigned int i; for (i = 0; i < ARRAY_SIZE(index_bits_to_maxindex) - 1; i++) index_bits_to_maxindex[i] = (1UL << (i + 1)) - 1; index_bits_to_maxindex[ARRAY_SIZE(index_bits_to_maxindex) - 1] = ~0UL; } /* * Maximum heap_index that can be stored in a PST with index_bits bits */ static inline unsigned long prio_tree_maxindex(unsigned int bits) { return index_bits_to_maxindex[bits - 1]; } /* * Extend a priority search tree so that it can store a node with heap_index * max_heap_index. In the worst case, this algorithm takes O((log n)^2). * However, this function is used rarely and the common case performance is * not bad. */ static struct prio_tree_node *prio_tree_expand(struct prio_tree_root *root, struct prio_tree_node *node, unsigned long max_heap_index) { struct prio_tree_node *first = NULL, *prev, *last = NULL; if (max_heap_index > prio_tree_maxindex(root->index_bits)) root->index_bits++; while (max_heap_index > prio_tree_maxindex(root->index_bits)) { root->index_bits++; if (prio_tree_empty(root)) continue; if (first == NULL) { first = root->prio_tree_node; prio_tree_remove(root, root->prio_tree_node); INIT_PRIO_TREE_NODE(first); last = first; } else { prev = last; last = root->prio_tree_node; prio_tree_remove(root, root->prio_tree_node); INIT_PRIO_TREE_NODE(last); prev->left = last; last->parent = prev; } } INIT_PRIO_TREE_NODE(node); if (first) { node->left = first; first->parent = node; } else last = node; if (!prio_tree_empty(root)) { last->left = root->prio_tree_node; last->left->parent = last; } root->prio_tree_node = node; return node; } /* * Replace a prio_tree_node with a new node and return the old node */ struct prio_tree_node *prio_tree_replace(struct prio_tree_root *root, struct prio_tree_node *old, struct prio_tree_node *node) { INIT_PRIO_TREE_NODE(node); if (prio_tree_root(old)) { assert(root->prio_tree_node == old); /* * We can reduce root->index_bits here. However, it is complex * and does not help much to improve performance (IMO). */ node->parent = node; root->prio_tree_node = node; } else { node->parent = old->parent; if (old->parent->left == old) old->parent->left = node; else old->parent->right = node; } if (!prio_tree_left_empty(old)) { node->left = old->left; old->left->parent = node; } if (!prio_tree_right_empty(old)) { node->right = old->right; old->right->parent = node; } return old; } /* * Insert a prio_tree_node @node into a radix priority search tree @root. The * algorithm typically takes O(log n) time where 'log n' is the number of bits * required to represent the maximum heap_index. In the worst case, the algo * can take O((log n)^2) - check prio_tree_expand. * * If a prior node with same radix_index and heap_index is already found in * the tree, then returns the address of the prior node. Otherwise, inserts * @node into the tree and returns @node. */ struct prio_tree_node *prio_tree_insert(struct prio_tree_root *root, struct prio_tree_node *node) { struct prio_tree_node *cur, *res = node; unsigned long radix_index, heap_index; unsigned long r_index, h_index, index, mask; int size_flag = 0; get_index(node, &radix_index, &heap_index); if (prio_tree_empty(root) || heap_index > prio_tree_maxindex(root->index_bits)) return prio_tree_expand(root, node, heap_index); cur = root->prio_tree_node; mask = 1UL << (root->index_bits - 1); while (mask) { get_index(cur, &r_index, &h_index); if (r_index == radix_index && h_index == heap_index) return cur; if (h_index < heap_index || (h_index == heap_index && r_index > radix_index)) { struct prio_tree_node *tmp = node; node = prio_tree_replace(root, cur, node); cur = tmp; /* swap indices */ index = r_index; r_index = radix_index; radix_index = index; index = h_index; h_index = heap_index; heap_index = index; } if (size_flag) index = heap_index - radix_index; else index = radix_index; if (index & mask) { if (prio_tree_right_empty(cur)) { INIT_PRIO_TREE_NODE(node); cur->right = node; node->parent = cur; return res; } else cur = cur->right; } else { if (prio_tree_left_empty(cur)) { INIT_PRIO_TREE_NODE(node); cur->left = node; node->parent = cur; return res; } else cur = cur->left; } mask >>= 1; if (!mask) { mask = 1UL << (BITS_PER_LONG - 1); size_flag = 1; } } /* Should not reach here */ assert(0); return NULL; } /* * Remove a prio_tree_node @node from a radix priority search tree @root. The * algorithm takes O(log n) time where 'log n' is the number of bits required * to represent the maximum heap_index. */ void prio_tree_remove(struct prio_tree_root *root, struct prio_tree_node *node) { struct prio_tree_node *cur; unsigned long r_index, h_index_right, h_index_left; cur = node; while (!prio_tree_left_empty(cur) || !prio_tree_right_empty(cur)) { if (!prio_tree_left_empty(cur)) get_index(cur->left, &r_index, &h_index_left); else { cur = cur->right; continue; } if (!prio_tree_right_empty(cur)) get_index(cur->right, &r_index, &h_index_right); else { cur = cur->left; continue; } /* both h_index_left and h_index_right cannot be 0 */ if (h_index_left >= h_index_right) cur = cur->left; else cur = cur->right; } if (prio_tree_root(cur)) { assert(root->prio_tree_node == cur); INIT_PRIO_TREE_ROOT(root); return; } if (cur->parent->right == cur) cur->parent->right = cur->parent; else cur->parent->left = cur->parent; while (cur != node) cur = prio_tree_replace(root, cur->parent, cur); } /* * Following functions help to enumerate all prio_tree_nodes in the tree that * overlap with the input interval X [radix_index, heap_index]. The enumeration * takes O(log n + m) time where 'log n' is the height of the tree (which is * proportional to # of bits required to represent the maximum heap_index) and * 'm' is the number of prio_tree_nodes that overlap the interval X. */ static struct prio_tree_node *prio_tree_left(struct prio_tree_iter *iter, unsigned long *r_index, unsigned long *h_index) { if (prio_tree_left_empty(iter->cur)) return NULL; get_index(iter->cur->left, r_index, h_index); if (iter->r_index <= *h_index) { iter->cur = iter->cur->left; iter->mask >>= 1; if (iter->mask) { if (iter->size_level) iter->size_level++; } else { if (iter->size_level) { assert(prio_tree_left_empty(iter->cur)); assert(prio_tree_right_empty(iter->cur)); iter->size_level++; iter->mask = ULONG_MAX; } else { iter->size_level = 1; iter->mask = 1UL << (BITS_PER_LONG - 1); } } return iter->cur; } return NULL; } static struct prio_tree_node *prio_tree_right(struct prio_tree_iter *iter, unsigned long *r_index, unsigned long *h_index) { unsigned long value; if (prio_tree_right_empty(iter->cur)) return NULL; if (iter->size_level) value = iter->value; else value = iter->value | iter->mask; if (iter->h_index < value) return NULL; get_index(iter->cur->right, r_index, h_index); if (iter->r_index <= *h_index) { iter->cur = iter->cur->right; iter->mask >>= 1; iter->value = value; if (iter->mask) { if (iter->size_level) iter->size_level++; } else { if (iter->size_level) { assert(prio_tree_left_empty(iter->cur)); assert(prio_tree_right_empty(iter->cur)); iter->size_level++; iter->mask = ULONG_MAX; } else { iter->size_level = 1; iter->mask = 1UL << (BITS_PER_LONG - 1); } } return iter->cur; } return NULL; } static struct prio_tree_node *prio_tree_parent(struct prio_tree_iter *iter) { iter->cur = iter->cur->parent; if (iter->mask == ULONG_MAX) iter->mask = 1UL; else if (iter->size_level == 1) iter->mask = 1UL; else iter->mask <<= 1; if (iter->size_level) iter->size_level--; if (!iter->size_level && (iter->value & iter->mask)) iter->value ^= iter->mask; return iter->cur; } static inline int overlap(struct prio_tree_iter *iter, unsigned long r_index, unsigned long h_index) { return iter->h_index >= r_index && iter->r_index <= h_index; } /* * prio_tree_first: * * Get the first prio_tree_node that overlaps with the interval [radix_index, * heap_index]. Note that always radix_index <= heap_index. We do a pre-order * traversal of the tree. */ static struct prio_tree_node *prio_tree_first(struct prio_tree_iter *iter) { struct prio_tree_root *root; unsigned long r_index, h_index; INIT_PRIO_TREE_ITER(iter); root = iter->root; if (prio_tree_empty(root)) return NULL; get_index(root->prio_tree_node, &r_index, &h_index); if (iter->r_index > h_index) return NULL; iter->mask = 1UL << (root->index_bits - 1); iter->cur = root->prio_tree_node; while (1) { if (overlap(iter, r_index, h_index)) return iter->cur; if (prio_tree_left(iter, &r_index, &h_index)) continue; if (prio_tree_right(iter, &r_index, &h_index)) continue; break; } return NULL; } /* * prio_tree_next: * * Get the next prio_tree_node that overlaps with the input interval in iter */ struct prio_tree_node *prio_tree_next(struct prio_tree_iter *iter) { unsigned long r_index, h_index; if (iter->cur == NULL) return prio_tree_first(iter); repeat: while (prio_tree_left(iter, &r_index, &h_index)) if (overlap(iter, r_index, h_index)) return iter->cur; while (!prio_tree_right(iter, &r_index, &h_index)) { while (!prio_tree_root(iter->cur) && iter->cur->parent->right == iter->cur) prio_tree_parent(iter); if (prio_tree_root(iter->cur)) return NULL; prio_tree_parent(iter); } if (overlap(iter, r_index, h_index)) return iter->cur; goto repeat; } fio-2.1.3/lib/prio_tree.h000066400000000000000000000042231222032232000151660ustar00rootroot00000000000000#ifndef _LINUX_PRIO_TREE_H #define _LINUX_PRIO_TREE_H #include #include "../hash.h" struct prio_tree_node { struct prio_tree_node *left; struct prio_tree_node *right; struct prio_tree_node *parent; uint64_t start; uint64_t last; /* last location _in_ interval */ }; struct prio_tree_root { struct prio_tree_node *prio_tree_node; unsigned short index_bits; }; struct prio_tree_iter { struct prio_tree_node *cur; unsigned long mask; unsigned long value; int size_level; struct prio_tree_root *root; uint64_t r_index; uint64_t h_index; }; static inline void prio_tree_iter_init(struct prio_tree_iter *iter, struct prio_tree_root *root, uint64_t r_index, uint64_t h_index) { iter->root = root; iter->r_index = r_index; iter->h_index = h_index; iter->cur = NULL; } #define INIT_PRIO_TREE_ROOT(ptr) \ do { \ (ptr)->prio_tree_node = NULL; \ (ptr)->index_bits = 1; \ } while (0) #define INIT_PRIO_TREE_NODE(ptr) \ do { \ (ptr)->left = (ptr)->right = (ptr)->parent = (ptr); \ } while (0) #define INIT_PRIO_TREE_ITER(ptr) \ do { \ (ptr)->cur = NULL; \ (ptr)->mask = 0UL; \ (ptr)->value = 0UL; \ (ptr)->size_level = 0; \ } while (0) #define prio_tree_entry(ptr, type, member) \ ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) static inline int prio_tree_empty(const struct prio_tree_root *root) { return root->prio_tree_node == NULL; } static inline int prio_tree_root(const struct prio_tree_node *node) { return node->parent == node; } static inline int prio_tree_left_empty(const struct prio_tree_node *node) { return node->left == node; } static inline int prio_tree_right_empty(const struct prio_tree_node *node) { return node->right == node; } struct prio_tree_node *prio_tree_replace(struct prio_tree_root *root, struct prio_tree_node *old, struct prio_tree_node *node); struct prio_tree_node *prio_tree_insert(struct prio_tree_root *root, struct prio_tree_node *node); void prio_tree_remove(struct prio_tree_root *root, struct prio_tree_node *node); struct prio_tree_node *prio_tree_next(struct prio_tree_iter *iter); #endif /* _LINUX_PRIO_TREE_H */ fio-2.1.3/lib/rand.c000066400000000000000000000061111222032232000141130ustar00rootroot00000000000000/* This is a maximally equidistributed combined Tausworthe generator based on code from GNU Scientific Library 1.5 (30 Jun 2004) x_n = (s1_n ^ s2_n ^ s3_n) s1_{n+1} = (((s1_n & 4294967294) <<12) ^ (((s1_n <<13) ^ s1_n) >>19)) s2_{n+1} = (((s2_n & 4294967288) << 4) ^ (((s2_n << 2) ^ s2_n) >>25)) s3_{n+1} = (((s3_n & 4294967280) <<17) ^ (((s3_n << 3) ^ s3_n) >>11)) The period of this generator is about 2^88. From: P. L'Ecuyer, "Maximally Equidistributed Combined Tausworthe Generators", Mathematics of Computation, 65, 213 (1996), 203--213. This is available on the net from L'Ecuyer's home page, http://www.iro.umontreal.ca/~lecuyer/myftp/papers/tausme.ps ftp://ftp.iro.umontreal.ca/pub/simulation/lecuyer/papers/tausme.ps There is an erratum in the paper "Tables of Maximally Equidistributed Combined LFSR Generators", Mathematics of Computation, 68, 225 (1999), 261--269: http://www.iro.umontreal.ca/~lecuyer/myftp/papers/tausme2.ps ... the k_j most significant bits of z_j must be non- zero, for each j. (Note: this restriction also applies to the computer code given in [4], but was mistakenly not mentioned in that paper.) This affects the seeding procedure by imposing the requirement s1 > 1, s2 > 7, s3 > 15. */ #include #include "rand.h" #include "../hash.h" static inline int __seed(unsigned int x, unsigned int m) { return (x < m) ? x + m : x; } static void __init_rand(struct frand_state *state, unsigned int seed) { int cranks = 6; #define LCG(x, seed) ((x) * 69069 ^ (seed)) state->s1 = __seed(LCG((2^31) + (2^17) + (2^7), seed), 1); state->s2 = __seed(LCG(state->s1, seed), 7); state->s3 = __seed(LCG(state->s2, seed), 15); while (cranks--) __rand(state); } void init_rand(struct frand_state *state) { __init_rand(state, 1); } void init_rand_seed(struct frand_state *state, unsigned int seed) { __init_rand(state, seed); } void __fill_random_buf(void *buf, unsigned int len, unsigned long seed) { long *ptr = buf; while ((void *) ptr - buf < len) { *ptr = seed; ptr++; seed *= GOLDEN_RATIO_PRIME; seed >>= 3; } } unsigned long fill_random_buf(struct frand_state *fs, void *buf, unsigned int len) { unsigned long r = __rand(fs); if (sizeof(int) != sizeof(long *)) r *= (unsigned long) __rand(fs); __fill_random_buf(buf, len, r); return r; } unsigned long fill_random_buf_percentage(struct frand_state *fs, void *buf, unsigned int percentage, unsigned int segment, unsigned int len) { unsigned long r = __rand(fs); unsigned int this_len; if (percentage == 100) { memset(buf, 0, len); return 0; } if (segment > len) segment = len; if (sizeof(int) != sizeof(long *)) r *= (unsigned long) __rand(fs); while (len) { /* * Fill random chunk */ this_len = (segment * (100 - percentage)) / 100; if (this_len > len) this_len = len; __fill_random_buf(buf, this_len, r); len -= this_len; buf += this_len; if (this_len > len) this_len = len; memset(buf, 0, this_len); len -= this_len; buf += this_len; } return r; } fio-2.1.3/lib/rand.h000066400000000000000000000016171222032232000141260ustar00rootroot00000000000000#ifndef FIO_RAND_H #define FIO_RAND_H #define FRAND_MAX (-1U) struct frand_state { unsigned int s1, s2, s3; }; static inline unsigned int __rand(struct frand_state *state) { #define TAUSWORTHE(s,a,b,c,d) ((s&c)<>b) state->s1 = TAUSWORTHE(state->s1, 13, 19, 4294967294UL, 12); state->s2 = TAUSWORTHE(state->s2, 2, 25, 4294967288UL, 4); state->s3 = TAUSWORTHE(state->s3, 3, 11, 4294967280UL, 17); return (state->s1 ^ state->s2 ^ state->s3); } extern void init_rand(struct frand_state *); extern void init_rand_seed(struct frand_state *, unsigned int seed); extern void __fill_random_buf(void *buf, unsigned int len, unsigned long seed); extern unsigned long fill_random_buf(struct frand_state *, void *buf, unsigned int len); extern unsigned long fill_random_buf_percentage(struct frand_state *, void *buf, unsigned int percentage, unsigned int segment, unsigned int len); #endif fio-2.1.3/lib/rbtree.c000066400000000000000000000163241222032232000144610ustar00rootroot00000000000000/* Red Black Trees (C) 1999 Andrea Arcangeli (C) 2002 David Woodhouse This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA linux/lib/rbtree.c */ #include "rbtree.h" static void __rb_rotate_left(struct rb_node *node, struct rb_root *root) { struct rb_node *right = node->rb_right; struct rb_node *parent = rb_parent(node); if ((node->rb_right = right->rb_left)) rb_set_parent(right->rb_left, node); right->rb_left = node; rb_set_parent(right, parent); if (parent) { if (node == parent->rb_left) parent->rb_left = right; else parent->rb_right = right; } else root->rb_node = right; rb_set_parent(node, right); } static void __rb_rotate_right(struct rb_node *node, struct rb_root *root) { struct rb_node *left = node->rb_left; struct rb_node *parent = rb_parent(node); if ((node->rb_left = left->rb_right)) rb_set_parent(left->rb_right, node); left->rb_right = node; rb_set_parent(left, parent); if (parent) { if (node == parent->rb_right) parent->rb_right = left; else parent->rb_left = left; } else root->rb_node = left; rb_set_parent(node, left); } void rb_insert_color(struct rb_node *node, struct rb_root *root) { struct rb_node *parent, *gparent; while ((parent = rb_parent(node)) && rb_is_red(parent)) { gparent = rb_parent(parent); if (parent == gparent->rb_left) { { register struct rb_node *uncle = gparent->rb_right; if (uncle && rb_is_red(uncle)) { rb_set_black(uncle); rb_set_black(parent); rb_set_red(gparent); node = gparent; continue; } } if (parent->rb_right == node) { register struct rb_node *tmp; __rb_rotate_left(parent, root); tmp = parent; parent = node; node = tmp; } rb_set_black(parent); rb_set_red(gparent); __rb_rotate_right(gparent, root); } else { { register struct rb_node *uncle = gparent->rb_left; if (uncle && rb_is_red(uncle)) { rb_set_black(uncle); rb_set_black(parent); rb_set_red(gparent); node = gparent; continue; } } if (parent->rb_left == node) { register struct rb_node *tmp; __rb_rotate_right(parent, root); tmp = parent; parent = node; node = tmp; } rb_set_black(parent); rb_set_red(gparent); __rb_rotate_left(gparent, root); } } rb_set_black(root->rb_node); } static void __rb_erase_color(struct rb_node *node, struct rb_node *parent, struct rb_root *root) { struct rb_node *other; while ((!node || rb_is_black(node)) && node != root->rb_node) { if (parent->rb_left == node) { other = parent->rb_right; if (rb_is_red(other)) { rb_set_black(other); rb_set_red(parent); __rb_rotate_left(parent, root); other = parent->rb_right; } if ((!other->rb_left || rb_is_black(other->rb_left)) && (!other->rb_right || rb_is_black(other->rb_right))) { rb_set_red(other); node = parent; parent = rb_parent(node); } else { if (!other->rb_right || rb_is_black(other->rb_right)) { struct rb_node *o_left; if ((o_left = other->rb_left)) rb_set_black(o_left); rb_set_red(other); __rb_rotate_right(other, root); other = parent->rb_right; } rb_set_color(other, rb_color(parent)); rb_set_black(parent); if (other->rb_right) rb_set_black(other->rb_right); __rb_rotate_left(parent, root); node = root->rb_node; break; } } else { other = parent->rb_left; if (rb_is_red(other)) { rb_set_black(other); rb_set_red(parent); __rb_rotate_right(parent, root); other = parent->rb_left; } if ((!other->rb_left || rb_is_black(other->rb_left)) && (!other->rb_right || rb_is_black(other->rb_right))) { rb_set_red(other); node = parent; parent = rb_parent(node); } else { if (!other->rb_left || rb_is_black(other->rb_left)) { register struct rb_node *o_right; if ((o_right = other->rb_right)) rb_set_black(o_right); rb_set_red(other); __rb_rotate_left(other, root); other = parent->rb_left; } rb_set_color(other, rb_color(parent)); rb_set_black(parent); if (other->rb_left) rb_set_black(other->rb_left); __rb_rotate_right(parent, root); node = root->rb_node; break; } } } if (node) rb_set_black(node); } void rb_erase(struct rb_node *node, struct rb_root *root) { struct rb_node *child, *parent; int color; if (!node->rb_left) child = node->rb_right; else if (!node->rb_right) child = node->rb_left; else { struct rb_node *old = node, *left; node = node->rb_right; while ((left = node->rb_left) != NULL) node = left; child = node->rb_right; parent = rb_parent(node); color = rb_color(node); if (child) rb_set_parent(child, parent); if (parent == old) { parent->rb_right = child; parent = node; } else parent->rb_left = child; node->rb_parent_color = old->rb_parent_color; node->rb_right = old->rb_right; node->rb_left = old->rb_left; if (rb_parent(old)) { if (rb_parent(old)->rb_left == old) rb_parent(old)->rb_left = node; else rb_parent(old)->rb_right = node; } else root->rb_node = node; rb_set_parent(old->rb_left, node); if (old->rb_right) rb_set_parent(old->rb_right, node); goto color; } parent = rb_parent(node); color = rb_color(node); if (child) rb_set_parent(child, parent); if (parent) { if (parent->rb_left == node) parent->rb_left = child; else parent->rb_right = child; } else root->rb_node = child; color: if (color == RB_BLACK) __rb_erase_color(child, parent, root); } /* * This function returns the first node (in sort order) of the tree. */ struct rb_node *rb_first(struct rb_root *root) { struct rb_node *n; n = root->rb_node; if (!n) return NULL; while (n->rb_left) n = n->rb_left; return n; } struct rb_node *rb_next(const struct rb_node *node) { struct rb_node *parent; if (RB_EMPTY_NODE(node)) return NULL; /* * If we have a right-hand child, go down and then left as far * as we can. */ if (node->rb_right) { node = node->rb_right; while (node->rb_left) node=node->rb_left; return (struct rb_node *)node; } /* * No right-hand children. Everything down and left is smaller than us, * so any 'next' node must be in the general direction of our parent. * Go up the tree; any time the ancestor is a right-hand child of its * parent, keep going up. First time it's a left-hand child of its * parent, said parent is our 'next' node. */ while ((parent = rb_parent(node)) && node == parent->rb_right) node = parent; return parent; } fio-2.1.3/lib/rbtree.h000066400000000000000000000110021222032232000144520ustar00rootroot00000000000000/* Red Black Trees (C) 1999 Andrea Arcangeli This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA linux/include/linux/rbtree.h To use rbtrees you'll have to implement your own insert and search cores. This will avoid us to use callbacks and to drop drammatically performances. I know it's not the cleaner way, but in C (not in C++) to get performances and genericity... Some example of insert and search follows here. The search is a plain normal search over an ordered tree. The insert instead must be implemented int two steps: as first thing the code must insert the element in order as a red leaf in the tree, then the support library function rb_insert_color() must be called. Such function will do the not trivial work to rebalance the rbtree if necessary. ----------------------------------------------------------------------- static inline struct page * rb_search_page_cache(struct inode * inode, unsigned long offset) { struct rb_node * n = inode->i_rb_page_cache.rb_node; struct page * page; while (n) { page = rb_entry(n, struct page, rb_page_cache); if (offset < page->offset) n = n->rb_left; else if (offset > page->offset) n = n->rb_right; else return page; } return NULL; } static inline struct page * __rb_insert_page_cache(struct inode * inode, unsigned long offset, struct rb_node * node) { struct rb_node ** p = &inode->i_rb_page_cache.rb_node; struct rb_node * parent = NULL; struct page * page; while (*p) { parent = *p; page = rb_entry(parent, struct page, rb_page_cache); if (offset < page->offset) p = &(*p)->rb_left; else if (offset > page->offset) p = &(*p)->rb_right; else return page; } rb_link_node(node, parent, p); return NULL; } static inline struct page * rb_insert_page_cache(struct inode * inode, unsigned long offset, struct rb_node * node) { struct page * ret; if ((ret = __rb_insert_page_cache(inode, offset, node))) goto out; rb_insert_color(node, &inode->i_rb_page_cache); out: return ret; } ----------------------------------------------------------------------- */ #ifndef _LINUX_RBTREE_H #define _LINUX_RBTREE_H #include #include struct rb_node { intptr_t rb_parent_color; #define RB_RED 0 #define RB_BLACK 1 struct rb_node *rb_right; struct rb_node *rb_left; } __attribute__((aligned(sizeof(long)))); /* The alignment might seem pointless, but allegedly CRIS needs it */ struct rb_root { struct rb_node *rb_node; }; #define rb_parent(r) ((struct rb_node *)((r)->rb_parent_color & ~3)) #define rb_color(r) ((r)->rb_parent_color & 1) #define rb_is_red(r) (!rb_color(r)) #define rb_is_black(r) rb_color(r) #define rb_set_red(r) do { (r)->rb_parent_color &= ~1; } while (0) #define rb_set_black(r) do { (r)->rb_parent_color |= 1; } while (0) static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p) { rb->rb_parent_color = (rb->rb_parent_color & 3) | (uintptr_t)p; } static inline void rb_set_color(struct rb_node *rb, int color) { rb->rb_parent_color = (rb->rb_parent_color & ~1) | color; } #define RB_ROOT (struct rb_root) { NULL, } #define rb_entry(ptr, type, member) container_of(ptr, type, member) #define RB_EMPTY_ROOT(root) ((root)->rb_node == NULL) #define RB_EMPTY_NODE(node) (rb_parent(node) == node) #define RB_CLEAR_NODE(node) (rb_set_parent(node, node)) extern void rb_insert_color(struct rb_node *, struct rb_root *); extern void rb_erase(struct rb_node *, struct rb_root *); /* Find logical next and previous nodes in a tree */ extern struct rb_node *rb_first(struct rb_root *); extern struct rb_node *rb_next(const struct rb_node *); static inline void rb_link_node(struct rb_node * node, struct rb_node * parent, struct rb_node ** rb_link) { node->rb_parent_color = (uintptr_t)parent; node->rb_left = node->rb_right = NULL; *rb_link = node; } #endif /* _LINUX_RBTREE_H */ fio-2.1.3/lib/strcasestr.c000066400000000000000000000005561222032232000153730ustar00rootroot00000000000000#include #include char *strcasestr(const char *s1, const char *s2) { const char *s = s1; const char *p = s2; do { if (!*p) return (char *) s1; if ((*p == *s) || (tolower(*p) == tolower(*s))) { ++p; ++s; } else { p = s2; if (!*s) return NULL; s = ++s1; } } while (1); return *p ? NULL : (char *) s1; } fio-2.1.3/lib/strcasestr.h000066400000000000000000000002641222032232000153740ustar00rootroot00000000000000#ifdef CONFIG_STRCASESTR #include #else #ifndef FIO_STRCASESTR_H #define FIO_STRCASESTR_H char *strcasestr(const char *haystack, const char *needle); #endif #endif fio-2.1.3/lib/strsep.c000066400000000000000000000005731222032232000145150ustar00rootroot00000000000000#include char *strsep(char **stringp, const char *delim) { char *s, *tok; const char *spanp; int c, sc; s = *stringp; if (!s) return NULL; tok = s; do { c = *s++; spanp = delim; do { sc = *spanp++; if (sc == c) { if (c == 0) s = NULL; else s[-1] = 0; *stringp = s; return tok; } } while (sc != 0); } while (1); } fio-2.1.3/lib/strsep.h000066400000000000000000000001401222032232000145100ustar00rootroot00000000000000#ifndef FIO_STRSEP_LIB_H #define FIO_STRSEP_LIB_H char *strsep(char **, const char *); #endif fio-2.1.3/lib/zipf.c000066400000000000000000000042231222032232000141410ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "ieee754.h" #include "../log.h" #include "zipf.h" #include "../minmax.h" #include "../hash.h" #define ZIPF_MAX_GEN 10000000 static void zipf_update(struct zipf_state *zs) { unsigned long to_gen; unsigned int i; /* * It can become very costly to generate long sequences. Just cap it at * 10M max, that should be doable in 1-2s on even slow machines. * Precision will take a slight hit, but nothing major. */ to_gen = min(zs->nranges, ZIPF_MAX_GEN); for (i = 0; i < to_gen; i++) zs->zetan += pow(1.0 / (double) (i + 1), zs->theta); } static void shared_rand_init(struct zipf_state *zs, unsigned long nranges, unsigned int seed) { memset(zs, 0, sizeof(*zs)); zs->nranges = nranges; init_rand_seed(&zs->rand, seed); zs->rand_off = __rand(&zs->rand); } void zipf_init(struct zipf_state *zs, unsigned long nranges, double theta, unsigned int seed) { shared_rand_init(zs, nranges, seed); zs->theta = theta; zs->zeta2 = pow(1.0, zs->theta) + pow(0.5, zs->theta); zipf_update(zs); } unsigned long long zipf_next(struct zipf_state *zs) { double alpha, eta, rand_uni, rand_z; unsigned long long n = zs->nranges; unsigned long long val; alpha = 1.0 / (1.0 - zs->theta); eta = (1.0 - pow(2.0 / n, 1.0 - zs->theta)) / (1.0 - zs->zeta2 / zs->zetan); rand_uni = (double) __rand(&zs->rand) / (double) FRAND_MAX; rand_z = rand_uni * zs->zetan; if (rand_z < 1.0) val = 1; else if (rand_z < (1.0 + pow(0.5, zs->theta))) val = 2; else val = 1 + (unsigned long long)(n * pow(eta*rand_uni - eta + 1.0, alpha)); return (__hash_u64(val - 1) + zs->rand_off) % zs->nranges; } void pareto_init(struct zipf_state *zs, unsigned long nranges, double h, unsigned int seed) { shared_rand_init(zs, nranges, seed); zs->pareto_pow = log(h) / log(1.0 - h); } unsigned long long pareto_next(struct zipf_state *zs) { double rand = (double) __rand(&zs->rand) / (double) FRAND_MAX; unsigned long long n = zs->nranges - 1; return (__hash_u64(n * pow(rand, zs->pareto_pow)) + zs->rand_off) % zs->nranges; } fio-2.1.3/lib/zipf.h000066400000000000000000000010331222032232000141420ustar00rootroot00000000000000#ifndef FIO_ZIPF_H #define FIO_ZIPF_H #include #include "rand.h" struct zipf_state { uint64_t nranges; double theta; double zeta2; double zetan; double pareto_pow; struct frand_state rand; uint64_t rand_off; }; void zipf_init(struct zipf_state *zs, unsigned long nranges, double theta, unsigned int seed); unsigned long long zipf_next(struct zipf_state *zs); void pareto_init(struct zipf_state *zs, unsigned long nranges, double h, unsigned int seed); unsigned long long pareto_next(struct zipf_state *zs); #endif fio-2.1.3/libfio.c000066400000000000000000000117411222032232000136720ustar00rootroot00000000000000/* * fio - the flexible io tester * * Copyright (C) 2005 Jens Axboe * Copyright (C) 2006-2012 Jens Axboe * * The license below covers all files distributed with fio unless otherwise * noted in the file itself. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include "fio.h" #include "smalloc.h" #include "os/os.h" /* * Just expose an empty list, if the OS does not support disk util stats */ #ifndef FIO_HAVE_DISK_UTIL FLIST_HEAD(disk_list); #endif unsigned long arch_flags = 0; uintptr_t page_mask = 0; uintptr_t page_size = 0; static const char *fio_os_strings[os_nr] = { "Invalid", "Linux", "AIX", "FreeBSD", "HP-UX", "OSX", "NetBSD", "Solaris", "Windows" }; static const char *fio_arch_strings[arch_nr] = { "Invalid", "x86-64", "x86", "ppc", "ia64", "s390", "alpha", "sparc", "sparc64", "arm", "sh", "hppa", "generic" }; static void reset_io_counters(struct thread_data *td) { int ddir; for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) { td->stat_io_bytes[ddir] = 0; td->this_io_bytes[ddir] = 0; td->stat_io_blocks[ddir] = 0; td->this_io_blocks[ddir] = 0; td->rate_bytes[ddir] = 0; td->rate_blocks[ddir] = 0; td->io_issues[ddir] = 0; } td->zone_bytes = 0; td->last_was_sync = 0; td->rwmix_issues = 0; /* * reset file done count if we are to start over */ if (td->o.time_based || td->o.loops || td->o.do_verify) td->nr_done_files = 0; } void clear_io_state(struct thread_data *td) { struct fio_file *f; unsigned int i; reset_io_counters(td); close_files(td); for_each_file(td, f, i) fio_file_clear_done(f); /* * Set the same seed to get repeatable runs */ td_fill_rand_seeds(td); } void reset_all_stats(struct thread_data *td) { struct timeval tv; int i; reset_io_counters(td); for (i = 0; i < DDIR_RWDIR_CNT; i++) { td->io_bytes[i] = 0; td->io_blocks[i] = 0; td->io_issues[i] = 0; td->ts.total_io_u[i] = 0; td->ts.runtime[i] = 0; td->rwmix_issues = 0; } fio_gettime(&tv, NULL); memcpy(&td->epoch, &tv, sizeof(tv)); memcpy(&td->start, &tv, sizeof(tv)); } void reset_fio_state(void) { groupid = 0; thread_number = 0; stat_number = 0; done_secs = 0; } const char *fio_get_os_string(int nr) { if (nr < os_nr) return fio_os_strings[nr]; return NULL; } const char *fio_get_arch_string(int nr) { if (nr < arch_nr) return fio_arch_strings[nr]; return NULL; } void td_set_runstate(struct thread_data *td, int runstate) { if (td->runstate == runstate) return; dprint(FD_PROCESS, "pid=%d: runstate %d -> %d\n", (int) td->pid, td->runstate, runstate); td->runstate = runstate; } void fio_terminate_threads(int group_id) { struct thread_data *td; pid_t pid = getpid(); int i; dprint(FD_PROCESS, "terminate group_id=%d\n", group_id); for_each_td(td, i) { if (group_id == TERMINATE_ALL || groupid == td->groupid) { dprint(FD_PROCESS, "setting terminate on %s/%d\n", td->o.name, (int) td->pid); td->terminate = 1; td->o.start_delay = 0; /* * if the thread is running, just let it exit */ if (!td->pid || pid == td->pid) continue; else if (td->runstate < TD_RAMP) kill(td->pid, SIGTERM); else { struct ioengine_ops *ops = td->io_ops; if (ops && ops->terminate) ops->terminate(td); } } } } static int endian_check(void) { union { uint8_t c[8]; uint64_t v; } u; int le = 0, be = 0; u.v = 0x12; if (u.c[7] == 0x12) be = 1; else if (u.c[0] == 0x12) le = 1; #if defined(CONFIG_LITTLE_ENDIAN) if (be) return 1; #elif defined(CONFIG_BIG_ENDIAN) if (le) return 1; #else return 1; #endif if (!le && !be) return 1; return 0; } int initialize_fio(char *envp[]) { long ps; if (endian_check()) { log_err("fio: endianness settings appear wrong.\n"); log_err("fio: please report this to fio@vger.kernel.org\n"); return 1; } #if !defined(CONFIG_GETTIMEOFDAY) && !defined(CONFIG_CLOCK_GETTIME) #error "No available clock source!" #endif arch_init(envp); sinit(); /* * We need locale for number printing, if it isn't set then just * go with the US format. */ if (!getenv("LC_NUMERIC")) setlocale(LC_NUMERIC, "en_US"); ps = sysconf(_SC_PAGESIZE); if (ps < 0) { log_err("Failed to get page size\n"); return 1; } page_size = ps; page_mask = ps - 1; fio_keywords_init(); return 0; } fio-2.1.3/log.c000066400000000000000000000042511222032232000132050ustar00rootroot00000000000000#include #include #include #include #include #include "fio.h" int log_valist(const char *str, va_list args) { char buffer[1024]; size_t len; len = vsnprintf(buffer, sizeof(buffer), str, args); len = min(len, sizeof(buffer) - 1); if (is_backend) len = fio_server_text_output(FIO_LOG_INFO, buffer, len); if (log_syslog) syslog(LOG_INFO, "%s", buffer); else len = fwrite(buffer, len, 1, f_out); return len; } int log_local_buf(const char *buf, size_t len) { if (log_syslog) syslog(LOG_INFO, "%s", buf); else len = fwrite(buf, len, 1, f_out); return len; } int log_local(const char *format, ...) { char buffer[1024]; va_list args; size_t len; va_start(args, format); len = vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); len = min(len, sizeof(buffer) - 1); if (log_syslog) syslog(LOG_INFO, "%s", buffer); else len = fwrite(buffer, len, 1, f_out); return len; } int log_info(const char *format, ...) { char buffer[1024]; va_list args; size_t len; va_start(args, format); len = vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); len = min(len, sizeof(buffer) - 1); if (is_backend) return fio_server_text_output(FIO_LOG_INFO, buffer, len); else if (log_syslog) { syslog(LOG_INFO, "%s", buffer); return len; } else return fwrite(buffer, len, 1, f_out); } int log_info_flush(void) { if (is_backend || log_syslog) return 0; return fflush(f_out); } int log_err(const char *format, ...) { char buffer[1024]; va_list args; size_t len; va_start(args, format); len = vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); len = min(len, sizeof(buffer) - 1); if (is_backend) return fio_server_text_output(FIO_LOG_ERR, buffer, len); else if (log_syslog) { syslog(LOG_INFO, "%s", buffer); return len; } else { if (f_err != stderr) { int fio_unused ret; ret = fwrite(buffer, len, 1, stderr); } return fwrite(buffer, len, 1, f_err); } } const char *log_get_level(int level) { static const char *levels[] = { "Unknown", "Debug", "Info", "Error", "Unknown" }; if (level >= FIO_LOG_NR) level = FIO_LOG_NR; return levels[level]; } fio-2.1.3/log.h000066400000000000000000000011011222032232000132010ustar00rootroot00000000000000#ifndef FIO_LOG_H #define FIO_LOG_H #include #include extern FILE *f_out; extern FILE *f_err; extern int log_err(const char *format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); extern int log_info(const char *format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); extern int log_valist(const char *str, va_list); extern int log_local_buf(const char *buf, size_t); extern int log_info_flush(void); enum { FIO_LOG_DEBUG = 1, FIO_LOG_INFO = 2, FIO_LOG_ERR = 3, FIO_LOG_NR = 4, }; extern const char *log_get_level(int level); #endif fio-2.1.3/memalign.c000066400000000000000000000012271222032232000142150ustar00rootroot00000000000000#include #include #include #include "memalign.h" struct align_footer { unsigned int offset; }; #define PTR_ALIGN(ptr, mask) \ (char *) (((uintptr_t) ((ptr) + (mask)) & ~(mask))) void *fio_memalign(size_t alignment, size_t size) { struct align_footer *f; void *ptr, *ret = NULL; assert(!(alignment & (alignment - 1))); ptr = malloc(size + alignment + size + sizeof(*f) - 1); if (ptr) { ret = PTR_ALIGN(ptr, alignment); f = ret + size; f->offset = (uintptr_t) ret - (uintptr_t) ptr; } return ret; } void fio_memfree(void *ptr, size_t size) { struct align_footer *f = ptr + size; free(ptr - f->offset); } fio-2.1.3/memalign.h000066400000000000000000000002421222032232000142160ustar00rootroot00000000000000#ifndef FIO_MEMALIGN_H #define FIO_MEMALIGN_H extern void *fio_memalign(size_t alignment, size_t size); extern void fio_memfree(void *ptr, size_t size); #endif fio-2.1.3/memory.c000066400000000000000000000140551222032232000137370ustar00rootroot00000000000000/* * Memory helpers */ #include #include #include #include #include #include "fio.h" #ifndef FIO_NO_HAVE_SHM_H #include #endif void fio_unpin_memory(struct thread_data *td) { if (td->pinned_mem) { dprint(FD_MEM, "unpinning %llu bytes\n", td->o.lockmem); if (munlock(td->pinned_mem, td->o.lockmem) < 0) perror("munlock"); munmap(td->pinned_mem, td->o.lockmem); td->pinned_mem = NULL; } } int fio_pin_memory(struct thread_data *td) { unsigned long long phys_mem; if (!td->o.lockmem) return 0; dprint(FD_MEM, "pinning %llu bytes\n", td->o.lockmem); /* * Don't allow mlock of more than real_mem-128MB */ phys_mem = os_phys_mem(); if (phys_mem) { if ((td->o.lockmem + 128 * 1024 * 1024) > phys_mem) { td->o.lockmem = phys_mem - 128 * 1024 * 1024; log_info("fio: limiting mlocked memory to %lluMB\n", td->o.lockmem >> 20); } } td->pinned_mem = mmap(NULL, td->o.lockmem, PROT_READ | PROT_WRITE, MAP_PRIVATE | OS_MAP_ANON, -1, 0); if (td->pinned_mem == MAP_FAILED) { perror("malloc locked mem"); td->pinned_mem = NULL; return 1; } if (mlock(td->pinned_mem, td->o.lockmem) < 0) { perror("mlock"); munmap(td->pinned_mem, td->o.lockmem); td->pinned_mem = NULL; return 1; } return 0; } static int alloc_mem_shm(struct thread_data *td, unsigned int total_mem) { int flags = IPC_CREAT | S_IRUSR | S_IWUSR; if (td->o.mem_type == MEM_SHMHUGE) { unsigned long mask = td->o.hugepage_size - 1; flags |= SHM_HUGETLB; total_mem = (total_mem + mask) & ~mask; } td->shm_id = shmget(IPC_PRIVATE, total_mem, flags); dprint(FD_MEM, "shmget %u, %d\n", total_mem, td->shm_id); if (td->shm_id < 0) { td_verror(td, errno, "shmget"); if (geteuid() != 0 && (errno == ENOMEM || errno == EPERM)) log_err("fio: you may need to run this job as root\n"); if (td->o.mem_type == MEM_SHMHUGE) { if (errno == EINVAL) { log_err("fio: check that you have free huge" " pages and that hugepage-size is" " correct.\n"); } else if (errno == ENOSYS) { log_err("fio: your system does not appear to" " support huge pages.\n"); } else if (errno == ENOMEM) { log_err("fio: no huge pages available, do you" " need to alocate some? See HOWTO.\n"); } } return 1; } td->orig_buffer = shmat(td->shm_id, NULL, 0); dprint(FD_MEM, "shmat %d, %p\n", td->shm_id, td->orig_buffer); if (td->orig_buffer == (void *) -1) { td_verror(td, errno, "shmat"); td->orig_buffer = NULL; return 1; } return 0; } static void free_mem_shm(struct thread_data *td) { struct shmid_ds sbuf; dprint(FD_MEM, "shmdt/ctl %d %p\n", td->shm_id, td->orig_buffer); shmdt(td->orig_buffer); shmctl(td->shm_id, IPC_RMID, &sbuf); } static int alloc_mem_mmap(struct thread_data *td, size_t total_mem) { int flags = 0; td->mmapfd = 1; if (td->o.mem_type == MEM_MMAPHUGE) { unsigned long mask = td->o.hugepage_size - 1; /* TODO: make sure the file is a real hugetlbfs file */ if (!td->o.mmapfile) flags |= MAP_HUGETLB; total_mem = (total_mem + mask) & ~mask; } if (td->o.mmapfile) { td->mmapfd = open(td->o.mmapfile, O_RDWR|O_CREAT, 0644); if (td->mmapfd < 0) { td_verror(td, errno, "open mmap file"); td->orig_buffer = NULL; return 1; } if (td->o.mem_type != MEM_MMAPHUGE && ftruncate(td->mmapfd, total_mem) < 0) { td_verror(td, errno, "truncate mmap file"); td->orig_buffer = NULL; return 1; } if (td->o.mem_type == MEM_MMAPHUGE) flags |= MAP_SHARED; else flags |= MAP_PRIVATE; } else flags |= OS_MAP_ANON | MAP_PRIVATE; td->orig_buffer = mmap(NULL, total_mem, PROT_READ | PROT_WRITE, flags, td->mmapfd, 0); dprint(FD_MEM, "mmap %llu/%d %p\n", (unsigned long long) total_mem, td->mmapfd, td->orig_buffer); if (td->orig_buffer == MAP_FAILED) { td_verror(td, errno, "mmap"); td->orig_buffer = NULL; if (td->mmapfd) { close(td->mmapfd); unlink(td->o.mmapfile); } return 1; } return 0; } static void free_mem_mmap(struct thread_data *td, size_t total_mem) { dprint(FD_MEM, "munmap %llu %p\n", (unsigned long long) total_mem, td->orig_buffer); munmap(td->orig_buffer, td->orig_buffer_size); if (td->o.mmapfile) { close(td->mmapfd); unlink(td->o.mmapfile); free(td->o.mmapfile); } } static int alloc_mem_malloc(struct thread_data *td, size_t total_mem) { td->orig_buffer = malloc(total_mem); dprint(FD_MEM, "malloc %llu %p\n", (unsigned long long) total_mem, td->orig_buffer); return td->orig_buffer == NULL; } static void free_mem_malloc(struct thread_data *td) { dprint(FD_MEM, "free malloc mem %p\n", td->orig_buffer); free(td->orig_buffer); } /* * Set up the buffer area we need for io. */ int allocate_io_mem(struct thread_data *td) { size_t total_mem; int ret = 0; if (td->io_ops->flags & FIO_NOIO) return 0; total_mem = td->orig_buffer_size; if (td->o.odirect || td->o.mem_align || (td->io_ops->flags & FIO_MEMALIGN)) { total_mem += page_mask; if (td->o.mem_align && td->o.mem_align > page_size) total_mem += td->o.mem_align - page_size; } dprint(FD_MEM, "Alloc %llu for buffers\n", (unsigned long long) total_mem); if (td->o.mem_type == MEM_MALLOC) ret = alloc_mem_malloc(td, total_mem); else if (td->o.mem_type == MEM_SHM || td->o.mem_type == MEM_SHMHUGE) ret = alloc_mem_shm(td, total_mem); else if (td->o.mem_type == MEM_MMAP || td->o.mem_type == MEM_MMAPHUGE) ret = alloc_mem_mmap(td, total_mem); else { log_err("fio: bad mem type: %d\n", td->o.mem_type); ret = 1; } if (ret) td_verror(td, ENOMEM, "iomem allocation"); return ret; } void free_io_mem(struct thread_data *td) { unsigned int total_mem; total_mem = td->orig_buffer_size; if (td->o.odirect) total_mem += page_mask; if (td->o.mem_type == MEM_MALLOC) free_mem_malloc(td); else if (td->o.mem_type == MEM_SHM || td->o.mem_type == MEM_SHMHUGE) free_mem_shm(td); else if (td->o.mem_type == MEM_MMAP || td->o.mem_type == MEM_MMAPHUGE) free_mem_mmap(td, total_mem); else log_err("Bad memory type %u\n", td->o.mem_type); td->orig_buffer = NULL; td->orig_buffer_size = 0; } fio-2.1.3/minmax.h000066400000000000000000000002571222032232000137240ustar00rootroot00000000000000#ifndef FIO_MIN_MAX_H #define FIO_MIN_MAX_H #ifndef min #define min(a, b) ((a) < (b) ? (a) : (b)) #endif #ifndef max #define max(a, b) ((a) > (b) ? (a) : (b)) #endif #endif fio-2.1.3/mutex.c000066400000000000000000000112071222032232000135650ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "fio.h" #include "log.h" #include "mutex.h" #include "arch/arch.h" #include "os/os.h" #include "helpers.h" #include "time.h" #include "gettime.h" void fio_mutex_remove(struct fio_mutex *mutex) { assert(mutex->magic == FIO_MUTEX_MAGIC); pthread_cond_destroy(&mutex->cond); munmap((void *) mutex, sizeof(*mutex)); } struct fio_mutex *fio_mutex_init(int value) { struct fio_mutex *mutex = NULL; pthread_mutexattr_t attr; pthread_condattr_t cond; int ret; mutex = (void *) mmap(NULL, sizeof(struct fio_mutex), PROT_READ | PROT_WRITE, OS_MAP_ANON | MAP_SHARED, -1, 0); if (mutex == MAP_FAILED) { perror("mmap mutex"); mutex = NULL; goto err; } mutex->value = value; mutex->magic = FIO_MUTEX_MAGIC; ret = pthread_mutexattr_init(&attr); if (ret) { log_err("pthread_mutexattr_init: %s\n", strerror(ret)); goto err; } /* * Not all platforms support process shared mutexes (FreeBSD) */ #ifdef FIO_HAVE_PSHARED_MUTEX ret = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); if (ret) { log_err("pthread_mutexattr_setpshared: %s\n", strerror(ret)); goto err; } #endif pthread_condattr_init(&cond); #ifdef FIO_HAVE_PSHARED_MUTEX pthread_condattr_setpshared(&cond, PTHREAD_PROCESS_SHARED); #endif pthread_cond_init(&mutex->cond, &cond); ret = pthread_mutex_init(&mutex->lock, &attr); if (ret) { log_err("pthread_mutex_init: %s\n", strerror(ret)); goto err; } pthread_condattr_destroy(&cond); pthread_mutexattr_destroy(&attr); return mutex; err: if (mutex) fio_mutex_remove(mutex); return NULL; } static int mutex_timed_out(struct timeval *t, unsigned int seconds) { return mtime_since_now(t) >= seconds * 1000; } int fio_mutex_down_timeout(struct fio_mutex *mutex, unsigned int seconds) { struct timeval tv_s; struct timespec t; int ret = 0; assert(mutex->magic == FIO_MUTEX_MAGIC); gettimeofday(&tv_s, NULL); t.tv_sec = tv_s.tv_sec + seconds; t.tv_nsec = tv_s.tv_usec * 1000; pthread_mutex_lock(&mutex->lock); while (!mutex->value && !ret) { mutex->waiters++; /* * Some platforms (FreeBSD 9?) seems to return timed out * way too early, double check. */ ret = pthread_cond_timedwait(&mutex->cond, &mutex->lock, &t); if (ret == ETIMEDOUT && !mutex_timed_out(&tv_s, seconds)) ret = 0; mutex->waiters--; } if (!ret) { mutex->value--; pthread_mutex_unlock(&mutex->lock); } return ret; } void fio_mutex_down(struct fio_mutex *mutex) { assert(mutex->magic == FIO_MUTEX_MAGIC); pthread_mutex_lock(&mutex->lock); while (!mutex->value) { mutex->waiters++; pthread_cond_wait(&mutex->cond, &mutex->lock); mutex->waiters--; } mutex->value--; pthread_mutex_unlock(&mutex->lock); } void fio_mutex_up(struct fio_mutex *mutex) { assert(mutex->magic == FIO_MUTEX_MAGIC); pthread_mutex_lock(&mutex->lock); read_barrier(); if (!mutex->value && mutex->waiters) pthread_cond_signal(&mutex->cond); mutex->value++; pthread_mutex_unlock(&mutex->lock); } void fio_rwlock_write(struct fio_rwlock *lock) { assert(lock->magic == FIO_RWLOCK_MAGIC); pthread_rwlock_wrlock(&lock->lock); } void fio_rwlock_read(struct fio_rwlock *lock) { assert(lock->magic == FIO_RWLOCK_MAGIC); pthread_rwlock_rdlock(&lock->lock); } void fio_rwlock_unlock(struct fio_rwlock *lock) { assert(lock->magic == FIO_RWLOCK_MAGIC); pthread_rwlock_unlock(&lock->lock); } void fio_rwlock_remove(struct fio_rwlock *lock) { assert(lock->magic == FIO_RWLOCK_MAGIC); munmap((void *) lock, sizeof(*lock)); } struct fio_rwlock *fio_rwlock_init(void) { struct fio_rwlock *lock; pthread_rwlockattr_t attr; int ret; lock = (void *) mmap(NULL, sizeof(struct fio_rwlock), PROT_READ | PROT_WRITE, OS_MAP_ANON | MAP_SHARED, -1, 0); if (lock == MAP_FAILED) { perror("mmap rwlock"); lock = NULL; goto err; } lock->magic = FIO_RWLOCK_MAGIC; ret = pthread_rwlockattr_init(&attr); if (ret) { log_err("pthread_rwlockattr_init: %s\n", strerror(ret)); goto err; } #ifdef FIO_HAVE_PSHARED_MUTEX ret = pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); if (ret) { log_err("pthread_rwlockattr_setpshared: %s\n", strerror(ret)); goto destroy_attr; } ret = pthread_rwlock_init(&lock->lock, &attr); #else ret = pthread_rwlock_init(&lock->lock, NULL); #endif if (ret) { log_err("pthread_rwlock_init: %s\n", strerror(ret)); goto destroy_attr; } pthread_rwlockattr_destroy(&attr); return lock; destroy_attr: pthread_rwlockattr_destroy(&attr); err: if (lock) fio_rwlock_remove(lock); return NULL; } fio-2.1.3/mutex.h000066400000000000000000000015651222032232000136000ustar00rootroot00000000000000#ifndef FIO_MUTEX_H #define FIO_MUTEX_H #include #define FIO_MUTEX_MAGIC 0x4d555445U #define FIO_RWLOCK_MAGIC 0x52574c4fU struct fio_mutex { pthread_mutex_t lock; pthread_cond_t cond; int value; int waiters; int magic; }; struct fio_rwlock { pthread_rwlock_t lock; int magic; }; enum { FIO_MUTEX_LOCKED = 0, FIO_MUTEX_UNLOCKED = 1, }; extern struct fio_mutex *fio_mutex_init(int); extern void fio_mutex_remove(struct fio_mutex *); extern void fio_mutex_up(struct fio_mutex *); extern void fio_mutex_down(struct fio_mutex *); extern int fio_mutex_down_timeout(struct fio_mutex *, unsigned int); extern void fio_rwlock_read(struct fio_rwlock *); extern void fio_rwlock_write(struct fio_rwlock *); extern void fio_rwlock_unlock(struct fio_rwlock *); extern struct fio_rwlock *fio_rwlock_init(void); extern void fio_rwlock_remove(struct fio_rwlock *); #endif fio-2.1.3/options.c000066400000000000000000002414211222032232000141210ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "fio.h" #include "verify.h" #include "parse.h" #include "lib/fls.h" #include "options.h" #include "crc/crc32c.h" /* * Check if mmap/mmaphuge has a :/foo/bar/file at the end. If so, return that. */ static char *get_opt_postfix(const char *str) { char *p = strstr(str, ":"); if (!p) return NULL; p++; strip_blank_front(&p); strip_blank_end(p); return strdup(p); } static int converthexchartoint(char a) { int base; switch (a) { case '0'...'9': base = '0'; break; case 'A'...'F': base = 'A' - 10; break; case 'a'...'f': base = 'a' - 10; break; default: base = 0; } return a - base; } static int bs_cmp(const void *p1, const void *p2) { const struct bssplit *bsp1 = p1; const struct bssplit *bsp2 = p2; return bsp1->perc < bsp2->perc; } static int bssplit_ddir(struct thread_options *o, int ddir, char *str) { struct bssplit *bssplit; unsigned int i, perc, perc_missing; unsigned int max_bs, min_bs; long long val; char *fname; o->bssplit_nr[ddir] = 4; bssplit = malloc(4 * sizeof(struct bssplit)); i = 0; max_bs = 0; min_bs = -1; while ((fname = strsep(&str, ":")) != NULL) { char *perc_str; if (!strlen(fname)) break; /* * grow struct buffer, if needed */ if (i == o->bssplit_nr[ddir]) { o->bssplit_nr[ddir] <<= 1; bssplit = realloc(bssplit, o->bssplit_nr[ddir] * sizeof(struct bssplit)); } perc_str = strstr(fname, "/"); if (perc_str) { *perc_str = '\0'; perc_str++; perc = atoi(perc_str); if (perc > 100) perc = 100; else if (!perc) perc = -1; } else perc = -1; if (str_to_decimal(fname, &val, 1, o)) { log_err("fio: bssplit conversion failed\n"); free(o->bssplit); return 1; } if (val > max_bs) max_bs = val; if (val < min_bs) min_bs = val; bssplit[i].bs = val; bssplit[i].perc = perc; i++; } o->bssplit_nr[ddir] = i; /* * Now check if the percentages add up, and how much is missing */ perc = perc_missing = 0; for (i = 0; i < o->bssplit_nr[ddir]; i++) { struct bssplit *bsp = &bssplit[i]; if (bsp->perc == (unsigned char) -1) perc_missing++; else perc += bsp->perc; } if (perc > 100) { log_err("fio: bssplit percentages add to more than 100%%\n"); free(bssplit); return 1; } /* * If values didn't have a percentage set, divide the remains between * them. */ if (perc_missing) { for (i = 0; i < o->bssplit_nr[ddir]; i++) { struct bssplit *bsp = &bssplit[i]; if (bsp->perc == (unsigned char) -1) bsp->perc = (100 - perc) / perc_missing; } } o->min_bs[ddir] = min_bs; o->max_bs[ddir] = max_bs; /* * now sort based on percentages, for ease of lookup */ qsort(bssplit, o->bssplit_nr[ddir], sizeof(struct bssplit), bs_cmp); o->bssplit[ddir] = bssplit; return 0; } static int str_bssplit_cb(void *data, const char *input) { struct thread_data *td = data; char *str, *p, *odir, *ddir; int ret = 0; if (parse_dryrun()) return 0; p = str = strdup(input); strip_blank_front(&str); strip_blank_end(str); odir = strchr(str, ','); if (odir) { ddir = strchr(odir + 1, ','); if (ddir) { ret = bssplit_ddir(&td->o, DDIR_TRIM, ddir + 1); if (!ret) *ddir = '\0'; } else { char *op; op = strdup(odir + 1); ret = bssplit_ddir(&td->o, DDIR_TRIM, op); free(op); } if (!ret) ret = bssplit_ddir(&td->o, DDIR_WRITE, odir + 1); if (!ret) { *odir = '\0'; ret = bssplit_ddir(&td->o, DDIR_READ, str); } } else { char *op; op = strdup(str); ret = bssplit_ddir(&td->o, DDIR_WRITE, op); free(op); if (!ret) { op = strdup(str); ret = bssplit_ddir(&td->o, DDIR_TRIM, op); free(op); } ret = bssplit_ddir(&td->o, DDIR_READ, str); } free(p); return ret; } static int str2error(char *str) { const char *err[] = { "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG", "ENOEXEC", "EBADF", "ECHILD", "EAGAIN", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK", "EBUSY", "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE", "EMFILE", "ENOTTY", "ETXTBSY","EFBIG", "ENOSPC", "ESPIPE", "EROFS","EMLINK", "EPIPE", "EDOM", "ERANGE" }; int i = 0, num = sizeof(err) / sizeof(void *); while (i < num) { if (!strcmp(err[i], str)) return i + 1; i++; } return 0; } static int ignore_error_type(struct thread_data *td, int etype, char *str) { unsigned int i; int *error; char *fname; if (etype >= ERROR_TYPE_CNT) { log_err("Illegal error type\n"); return 1; } td->o.ignore_error_nr[etype] = 4; error = malloc(4 * sizeof(struct bssplit)); i = 0; while ((fname = strsep(&str, ":")) != NULL) { if (!strlen(fname)) break; /* * grow struct buffer, if needed */ if (i == td->o.ignore_error_nr[etype]) { td->o.ignore_error_nr[etype] <<= 1; error = realloc(error, td->o.ignore_error_nr[etype] * sizeof(int)); } if (fname[0] == 'E') { error[i] = str2error(fname); } else { error[i] = atoi(fname); if (error[i] < 0) error[i] = error[i]; } if (!error[i]) { log_err("Unknown error %s, please use number value \n", fname); free(error); return 1; } i++; } if (i) { td->o.continue_on_error |= 1 << etype; td->o.ignore_error_nr[etype] = i; td->o.ignore_error[etype] = error; } return 0; } static int str_ignore_error_cb(void *data, const char *input) { struct thread_data *td = data; char *str, *p, *n; int type = 0, ret = 1; if (parse_dryrun()) return 0; p = str = strdup(input); strip_blank_front(&str); strip_blank_end(str); while (p) { n = strchr(p, ','); if (n) *n++ = '\0'; ret = ignore_error_type(td, type, p); if (ret) break; p = n; type++; } free(str); return ret; } static int str_rw_cb(void *data, const char *str) { struct thread_data *td = data; struct thread_options *o = &td->o; char *nr = get_opt_postfix(str); if (parse_dryrun()) return 0; o->ddir_seq_nr = 1; o->ddir_seq_add = 0; if (!nr) return 0; if (td_random(td)) o->ddir_seq_nr = atoi(nr); else { long long val; if (str_to_decimal(nr, &val, 1, o)) { log_err("fio: rw postfix parsing failed\n"); free(nr); return 1; } o->ddir_seq_add = val; } free(nr); return 0; } static int str_mem_cb(void *data, const char *mem) { struct thread_data *td = data; if (td->o.mem_type == MEM_MMAPHUGE || td->o.mem_type == MEM_MMAP) td->o.mmapfile = get_opt_postfix(mem); return 0; } static int fio_clock_source_cb(void *data, const char *str) { struct thread_data *td = data; fio_clock_source = td->o.clocksource; fio_clock_source_set = 1; fio_clock_init(); return 0; } static int str_rwmix_read_cb(void *data, unsigned long long *val) { struct thread_data *td = data; td->o.rwmix[DDIR_READ] = *val; td->o.rwmix[DDIR_WRITE] = 100 - *val; return 0; } static int str_rwmix_write_cb(void *data, unsigned long long *val) { struct thread_data *td = data; td->o.rwmix[DDIR_WRITE] = *val; td->o.rwmix[DDIR_READ] = 100 - *val; return 0; } static int str_exitall_cb(void) { exitall_on_terminate = 1; return 0; } #ifdef FIO_HAVE_CPU_AFFINITY static int str_cpumask_cb(void *data, unsigned long long *val) { struct thread_data *td = data; unsigned int i; long max_cpu; int ret; if (parse_dryrun()) return 0; ret = fio_cpuset_init(&td->o.cpumask); if (ret < 0) { log_err("fio: cpuset_init failed\n"); td_verror(td, ret, "fio_cpuset_init"); return 1; } max_cpu = cpus_online(); for (i = 0; i < sizeof(int) * 8; i++) { if ((1 << i) & *val) { if (i > max_cpu) { log_err("fio: CPU %d too large (max=%ld)\n", i, max_cpu); return 1; } dprint(FD_PARSE, "set cpu allowed %d\n", i); fio_cpu_set(&td->o.cpumask, i); } } td->o.cpumask_set = 1; return 0; } static int set_cpus_allowed(struct thread_data *td, os_cpu_mask_t *mask, const char *input) { char *cpu, *str, *p; long max_cpu; int ret = 0; ret = fio_cpuset_init(mask); if (ret < 0) { log_err("fio: cpuset_init failed\n"); td_verror(td, ret, "fio_cpuset_init"); return 1; } p = str = strdup(input); strip_blank_front(&str); strip_blank_end(str); max_cpu = cpus_online(); while ((cpu = strsep(&str, ",")) != NULL) { char *str2, *cpu2; int icpu, icpu2; if (!strlen(cpu)) break; str2 = cpu; icpu2 = -1; while ((cpu2 = strsep(&str2, "-")) != NULL) { if (!strlen(cpu2)) break; icpu2 = atoi(cpu2); } icpu = atoi(cpu); if (icpu2 == -1) icpu2 = icpu; while (icpu <= icpu2) { if (icpu >= FIO_MAX_CPUS) { log_err("fio: your OS only supports up to" " %d CPUs\n", (int) FIO_MAX_CPUS); ret = 1; break; } if (icpu > max_cpu) { log_err("fio: CPU %d too large (max=%ld)\n", icpu, max_cpu); ret = 1; break; } dprint(FD_PARSE, "set cpu allowed %d\n", icpu); fio_cpu_set(mask, icpu); icpu++; } if (ret) break; } free(p); if (!ret) td->o.cpumask_set = 1; return ret; } static int str_cpus_allowed_cb(void *data, const char *input) { struct thread_data *td = data; int ret; if (parse_dryrun()) return 0; ret = set_cpus_allowed(td, &td->o.cpumask, input); if (!ret) td->o.cpumask_set = 1; return ret; } static int str_verify_cpus_allowed_cb(void *data, const char *input) { struct thread_data *td = data; int ret; ret = set_cpus_allowed(td, &td->o.verify_cpumask, input); if (!ret) td->o.verify_cpumask_set = 1; return ret; } #endif #ifdef CONFIG_LIBNUMA static int str_numa_cpunodes_cb(void *data, char *input) { struct thread_data *td = data; if (parse_dryrun()) return 0; /* numa_parse_nodestring() parses a character string list * of nodes into a bit mask. The bit mask is allocated by * numa_allocate_nodemask(), so it should be freed by * numa_free_nodemask(). */ td->o.numa_cpunodesmask = numa_parse_nodestring(input); if (td->o.numa_cpunodesmask == NULL) { log_err("fio: numa_parse_nodestring failed\n"); td_verror(td, 1, "str_numa_cpunodes_cb"); return 1; } td->o.numa_cpumask_set = 1; return 0; } static int str_numa_mpol_cb(void *data, char *input) { struct thread_data *td = data; const char * const policy_types[] = { "default", "prefer", "bind", "interleave", "local", NULL }; int i; char *nodelist; if (parse_dryrun()) return 0; nodelist = strchr(input, ':'); if (nodelist) { /* NUL-terminate mode */ *nodelist++ = '\0'; } for (i = 0; i <= MPOL_LOCAL; i++) { if (!strcmp(input, policy_types[i])) { td->o.numa_mem_mode = i; break; } } if (i > MPOL_LOCAL) { log_err("fio: memory policy should be: default, prefer, bind, interleave, local\n"); goto out; } switch (td->o.numa_mem_mode) { case MPOL_PREFERRED: /* * Insist on a nodelist of one node only */ if (nodelist) { char *rest = nodelist; while (isdigit(*rest)) rest++; if (*rest) { log_err("fio: one node only for \'prefer\'\n"); goto out; } } else { log_err("fio: one node is needed for \'prefer\'\n"); goto out; } break; case MPOL_INTERLEAVE: /* * Default to online nodes with memory if no nodelist */ if (!nodelist) nodelist = strdup("all"); break; case MPOL_LOCAL: case MPOL_DEFAULT: /* * Don't allow a nodelist */ if (nodelist) { log_err("fio: NO nodelist for \'local\'\n"); goto out; } break; case MPOL_BIND: /* * Insist on a nodelist */ if (!nodelist) { log_err("fio: a nodelist is needed for \'bind\'\n"); goto out; } break; } /* numa_parse_nodestring() parses a character string list * of nodes into a bit mask. The bit mask is allocated by * numa_allocate_nodemask(), so it should be freed by * numa_free_nodemask(). */ switch (td->o.numa_mem_mode) { case MPOL_PREFERRED: td->o.numa_mem_prefer_node = atoi(nodelist); break; case MPOL_INTERLEAVE: case MPOL_BIND: td->o.numa_memnodesmask = numa_parse_nodestring(nodelist); if (td->o.numa_memnodesmask == NULL) { log_err("fio: numa_parse_nodestring failed\n"); td_verror(td, 1, "str_numa_memnodes_cb"); return 1; } break; case MPOL_LOCAL: case MPOL_DEFAULT: default: break; } td->o.numa_memmask_set = 1; return 0; out: return 1; } #endif static int str_fst_cb(void *data, const char *str) { struct thread_data *td = data; char *nr = get_opt_postfix(str); td->file_service_nr = 1; if (nr) { td->file_service_nr = atoi(nr); free(nr); } return 0; } #ifdef CONFIG_SYNC_FILE_RANGE static int str_sfr_cb(void *data, const char *str) { struct thread_data *td = data; char *nr = get_opt_postfix(str); td->sync_file_range_nr = 1; if (nr) { td->sync_file_range_nr = atoi(nr); free(nr); } return 0; } #endif static int str_random_distribution_cb(void *data, const char *str) { struct thread_data *td = data; double val; char *nr; if (parse_dryrun()) return 0; if (td->o.random_distribution == FIO_RAND_DIST_ZIPF) val = 1.1; else if (td->o.random_distribution == FIO_RAND_DIST_PARETO) val = 0.2; else return 0; nr = get_opt_postfix(str); if (nr && !str_to_float(nr, &val)) { log_err("fio: random postfix parsing failed\n"); free(nr); return 1; } free(nr); if (td->o.random_distribution == FIO_RAND_DIST_ZIPF) { if (val == 1.00) { log_err("fio: zipf theta must different than 1.0\n"); return 1; } td->o.zipf_theta.u.f = val; } else { if (val <= 0.00 || val >= 1.00) { log_err("fio: pareto input out of range (0 < input < 1.0)\n"); return 1; } td->o.pareto_h.u.f = val; } return 0; } /* * Return next file in the string. Files are separated with ':'. If the ':' * is escaped with a '\', then that ':' is part of the filename and does not * indicate a new file. */ static char *get_next_file_name(char **ptr) { char *str = *ptr; char *p, *start; if (!str || !strlen(str)) return NULL; start = str; do { /* * No colon, we are done */ p = strchr(str, ':'); if (!p) { *ptr = NULL; break; } /* * We got a colon, but it's the first character. Skip and * continue */ if (p == start) { str = ++start; continue; } if (*(p - 1) != '\\') { *p = '\0'; *ptr = p + 1; break; } memmove(p - 1, p, strlen(p) + 1); str = p; } while (1); return start; } static int str_filename_cb(void *data, const char *input) { struct thread_data *td = data; char *fname, *str, *p; p = str = strdup(input); strip_blank_front(&str); strip_blank_end(str); if (!td->files_index) td->o.nr_files = 0; while ((fname = get_next_file_name(&str)) != NULL) { if (!strlen(fname)) break; add_file(td, fname); td->o.nr_files++; } free(p); return 0; } static int str_directory_cb(void *data, const char fio_unused *str) { struct thread_data *td = data; struct stat sb; if (parse_dryrun()) return 0; if (lstat(td->o.directory, &sb) < 0) { int ret = errno; log_err("fio: %s is not a directory\n", td->o.directory); td_verror(td, ret, "lstat"); return 1; } if (!S_ISDIR(sb.st_mode)) { log_err("fio: %s is not a directory\n", td->o.directory); return 1; } return 0; } static int str_opendir_cb(void *data, const char fio_unused *str) { struct thread_data *td = data; if (parse_dryrun()) return 0; if (!td->files_index) td->o.nr_files = 0; return add_dir_files(td, td->o.opendir); } static int str_verify_pattern_cb(void *data, const char *input) { struct thread_data *td = data; long off; int i = 0, j = 0, len, k, base = 10, pattern_length; char *loc1, *loc2; loc1 = strstr(input, "0x"); loc2 = strstr(input, "0X"); if (loc1 || loc2) base = 16; off = strtol(input, NULL, base); if (off != LONG_MAX || errno != ERANGE) { while (off) { td->o.verify_pattern[i] = off & 0xff; off >>= 8; i++; } } else { len = strlen(input); k = len - 1; if (base == 16) { if (loc1) j = loc1 - input + 2; else j = loc2 - input + 2; } else return 1; if (len - j < MAX_PATTERN_SIZE * 2) { while (k >= j) { off = converthexchartoint(input[k--]); if (k >= j) off += (converthexchartoint(input[k--]) * 16); td->o.verify_pattern[i++] = (char) off; } } } /* * Fill the pattern all the way to the end. This greatly reduces * the number of memcpy's we have to do when verifying the IO. */ pattern_length = i; while (i > 1 && i * 2 <= MAX_PATTERN_SIZE) { memcpy(&td->o.verify_pattern[i], &td->o.verify_pattern[0], i); i *= 2; } /* * Fill remainder, if the pattern multiple ends up not being * MAX_PATTERN_SIZE. */ while (i > 1 && i < MAX_PATTERN_SIZE) { unsigned int b = min(pattern_length, MAX_PATTERN_SIZE - i); memcpy(&td->o.verify_pattern[i], &td->o.verify_pattern[0], b); i += b; } if (i == 1) { /* * The code in verify_io_u_pattern assumes a single byte pattern * fills the whole verify pattern buffer. */ memset(td->o.verify_pattern, td->o.verify_pattern[0], MAX_PATTERN_SIZE); } td->o.verify_pattern_bytes = i; /* * VERIFY_META could already be set */ if (td->o.verify == VERIFY_NONE) td->o.verify = VERIFY_PATTERN; return 0; } static int str_gtod_reduce_cb(void *data, int *il) { struct thread_data *td = data; int val = *il; td->o.disable_lat = !!val; td->o.disable_clat = !!val; td->o.disable_slat = !!val; td->o.disable_bw = !!val; td->o.clat_percentiles = !val; if (val) td->tv_cache_mask = 63; return 0; } static int str_gtod_cpu_cb(void *data, long long *il) { struct thread_data *td = data; int val = *il; td->o.gtod_cpu = val; td->o.gtod_offload = 1; return 0; } static int str_size_cb(void *data, unsigned long long *__val) { struct thread_data *td = data; unsigned long long v = *__val; if (parse_is_percent(v)) { td->o.size = 0; td->o.size_percent = -1ULL - v; } else td->o.size = v; return 0; } static int rw_verify(struct fio_option *o, void *data) { struct thread_data *td = data; if (read_only && td_write(td)) { log_err("fio: job <%s> has write bit set, but fio is in" " read-only mode\n", td->o.name); return 1; } return 0; } static int gtod_cpu_verify(struct fio_option *o, void *data) { #ifndef FIO_HAVE_CPU_AFFINITY struct thread_data *td = data; if (td->o.gtod_cpu) { log_err("fio: platform must support CPU affinity for" "gettimeofday() offloading\n"); return 1; } #endif return 0; } /* * Option grouping */ static struct opt_group fio_opt_groups[] = { { .name = "General", .mask = FIO_OPT_C_GENERAL, }, { .name = "I/O", .mask = FIO_OPT_C_IO, }, { .name = "File", .mask = FIO_OPT_C_FILE, }, { .name = "Statistics", .mask = FIO_OPT_C_STAT, }, { .name = "Logging", .mask = FIO_OPT_C_LOG, }, { .name = "Profiles", .mask = FIO_OPT_C_PROFILE, }, { .name = NULL, }, }; static struct opt_group *__opt_group_from_mask(struct opt_group *ogs, unsigned int *mask, unsigned int inv_mask) { struct opt_group *og; int i; if (*mask == inv_mask || !*mask) return NULL; for (i = 0; ogs[i].name; i++) { og = &ogs[i]; if (*mask & og->mask) { *mask &= ~(og->mask); return og; } } return NULL; } struct opt_group *opt_group_from_mask(unsigned int *mask) { return __opt_group_from_mask(fio_opt_groups, mask, FIO_OPT_C_INVALID); } static struct opt_group fio_opt_cat_groups[] = { { .name = "Rate", .mask = FIO_OPT_G_RATE, }, { .name = "Zone", .mask = FIO_OPT_G_ZONE, }, { .name = "Read/write mix", .mask = FIO_OPT_G_RWMIX, }, { .name = "Verify", .mask = FIO_OPT_G_VERIFY, }, { .name = "Trim", .mask = FIO_OPT_G_TRIM, }, { .name = "I/O Logging", .mask = FIO_OPT_G_IOLOG, }, { .name = "I/O Depth", .mask = FIO_OPT_G_IO_DEPTH, }, { .name = "I/O Flow", .mask = FIO_OPT_G_IO_FLOW, }, { .name = "Description", .mask = FIO_OPT_G_DESC, }, { .name = "Filename", .mask = FIO_OPT_G_FILENAME, }, { .name = "General I/O", .mask = FIO_OPT_G_IO_BASIC, }, { .name = "Cgroups", .mask = FIO_OPT_G_CGROUP, }, { .name = "Runtime", .mask = FIO_OPT_G_RUNTIME, }, { .name = "Process", .mask = FIO_OPT_G_PROCESS, }, { .name = "Job credentials / priority", .mask = FIO_OPT_G_CRED, }, { .name = "Clock settings", .mask = FIO_OPT_G_CLOCK, }, { .name = "I/O Type", .mask = FIO_OPT_G_IO_TYPE, }, { .name = "I/O Thinktime", .mask = FIO_OPT_G_THINKTIME, }, { .name = "Randomizations", .mask = FIO_OPT_G_RANDOM, }, { .name = "I/O buffers", .mask = FIO_OPT_G_IO_BUF, }, { .name = "Tiobench profile", .mask = FIO_OPT_G_TIOBENCH, }, { .name = NULL, } }; struct opt_group *opt_group_cat_from_mask(unsigned int *mask) { return __opt_group_from_mask(fio_opt_cat_groups, mask, FIO_OPT_G_INVALID); } /* * Map of job/command line options */ struct fio_option fio_options[FIO_MAX_OPTS] = { { .name = "description", .lname = "Description of job", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(description), .help = "Text job description", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_DESC, }, { .name = "name", .lname = "Job name", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(name), .help = "Name of this job", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_DESC, }, { .name = "filename", .lname = "Filename(s)", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(filename), .cb = str_filename_cb, .prio = -1, /* must come after "directory" */ .help = "File(s) to use for the workload", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_FILENAME, }, { .name = "directory", .lname = "Directory", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(directory), .cb = str_directory_cb, .help = "Directory to store files in", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_FILENAME, }, { .name = "filename_format", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(filename_format), .prio = -1, /* must come after "directory" */ .help = "Override default $jobname.$jobnum.$filenum naming", .def = "$jobname.$jobnum.$filenum", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_FILENAME, }, { .name = "lockfile", .lname = "Lockfile", .type = FIO_OPT_STR, .off1 = td_var_offset(file_lock_mode), .help = "Lock file when doing IO to it", .parent = "filename", .hide = 0, .def = "none", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_FILENAME, .posval = { { .ival = "none", .oval = FILE_LOCK_NONE, .help = "No file locking", }, { .ival = "exclusive", .oval = FILE_LOCK_EXCLUSIVE, .help = "Exclusive file lock", }, { .ival = "readwrite", .oval = FILE_LOCK_READWRITE, .help = "Read vs write lock", }, }, }, { .name = "opendir", .lname = "Open directory", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(opendir), .cb = str_opendir_cb, .help = "Recursively add files from this directory and down", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_FILENAME, }, { .name = "rw", .lname = "Read/write", .alias = "readwrite", .type = FIO_OPT_STR, .cb = str_rw_cb, .off1 = td_var_offset(td_ddir), .help = "IO direction", .def = "read", .verify = rw_verify, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BASIC, .posval = { { .ival = "read", .oval = TD_DDIR_READ, .help = "Sequential read", }, { .ival = "write", .oval = TD_DDIR_WRITE, .help = "Sequential write", }, { .ival = "trim", .oval = TD_DDIR_TRIM, .help = "Sequential trim", }, { .ival = "randread", .oval = TD_DDIR_RANDREAD, .help = "Random read", }, { .ival = "randwrite", .oval = TD_DDIR_RANDWRITE, .help = "Random write", }, { .ival = "randtrim", .oval = TD_DDIR_RANDTRIM, .help = "Random trim", }, { .ival = "rw", .oval = TD_DDIR_RW, .help = "Sequential read and write mix", }, { .ival = "readwrite", .oval = TD_DDIR_RW, .help = "Sequential read and write mix", }, { .ival = "randrw", .oval = TD_DDIR_RANDRW, .help = "Random read and write mix" }, }, }, { .name = "rw_sequencer", .lname = "RW Sequencer", .type = FIO_OPT_STR, .off1 = td_var_offset(rw_seq), .help = "IO offset generator modifier", .def = "sequential", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BASIC, .posval = { { .ival = "sequential", .oval = RW_SEQ_SEQ, .help = "Generate sequential offsets", }, { .ival = "identical", .oval = RW_SEQ_IDENT, .help = "Generate identical offsets", }, }, }, { .name = "ioengine", .lname = "IO Engine", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(ioengine), .help = "IO engine to use", .def = FIO_PREFERRED_ENGINE, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BASIC, .posval = { { .ival = "sync", .help = "Use read/write", }, { .ival = "psync", .help = "Use pread/pwrite", }, { .ival = "vsync", .help = "Use readv/writev", }, #ifdef CONFIG_PWRITEV { .ival = "pvsync", .help = "Use preadv/pwritev", }, #endif #ifdef CONFIG_LIBAIO { .ival = "libaio", .help = "Linux native asynchronous IO", }, #endif #ifdef CONFIG_POSIXAIO { .ival = "posixaio", .help = "POSIX asynchronous IO", }, #endif #ifdef CONFIG_SOLARISAIO { .ival = "solarisaio", .help = "Solaris native asynchronous IO", }, #endif #ifdef CONFIG_WINDOWSAIO { .ival = "windowsaio", .help = "Windows native asynchronous IO" }, #endif { .ival = "mmap", .help = "Memory mapped IO" }, #ifdef CONFIG_LINUX_SPLICE { .ival = "splice", .help = "splice/vmsplice based IO", }, { .ival = "netsplice", .help = "splice/vmsplice to/from the network", }, #endif #ifdef FIO_HAVE_SGIO { .ival = "sg", .help = "SCSI generic v3 IO", }, #endif { .ival = "null", .help = "Testing engine (no data transfer)", }, { .ival = "net", .help = "Network IO", }, { .ival = "cpuio", .help = "CPU cycle burner engine", }, #ifdef CONFIG_GUASI { .ival = "guasi", .help = "GUASI IO engine", }, #endif #ifdef FIO_HAVE_BINJECT { .ival = "binject", .help = "binject direct inject block engine", }, #endif #ifdef CONFIG_RDMA { .ival = "rdma", .help = "RDMA IO engine", }, #endif #ifdef CONFIG_FUSION_AW { .ival = "fusion-aw-sync", .help = "Fusion-io atomic write engine", }, #endif #ifdef CONFIG_LINUX_EXT4_MOVE_EXTENT { .ival = "e4defrag", .help = "ext4 defrag engine", }, #endif #ifdef CONFIG_LINUX_FALLOCATE { .ival = "falloc", .help = "fallocate() file based engine", }, #endif { .ival = "external", .help = "Load external engine (append name)", }, }, }, { .name = "iodepth", .lname = "IO Depth", .type = FIO_OPT_INT, .off1 = td_var_offset(iodepth), .help = "Number of IO buffers to keep in flight", .minval = 1, .interval = 1, .def = "1", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BASIC, }, { .name = "iodepth_batch", .lname = "IO Depth batch", .alias = "iodepth_batch_submit", .type = FIO_OPT_INT, .off1 = td_var_offset(iodepth_batch), .help = "Number of IO buffers to submit in one go", .parent = "iodepth", .hide = 1, .minval = 1, .interval = 1, .def = "1", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BASIC, }, { .name = "iodepth_batch_complete", .lname = "IO Depth batch complete", .type = FIO_OPT_INT, .off1 = td_var_offset(iodepth_batch_complete), .help = "Number of IO buffers to retrieve in one go", .parent = "iodepth", .hide = 1, .minval = 0, .interval = 1, .def = "1", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BASIC, }, { .name = "iodepth_low", .lname = "IO Depth batch low", .type = FIO_OPT_INT, .off1 = td_var_offset(iodepth_low), .help = "Low water mark for queuing depth", .parent = "iodepth", .hide = 1, .interval = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BASIC, }, { .name = "size", .lname = "Size", .type = FIO_OPT_STR_VAL, .cb = str_size_cb, .help = "Total size of device or files", .interval = 1024 * 1024, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, { .name = "fill_device", .lname = "Fill device", .alias = "fill_fs", .type = FIO_OPT_BOOL, .off1 = td_var_offset(fill_device), .help = "Write until an ENOSPC error occurs", .def = "0", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "filesize", .lname = "File size", .type = FIO_OPT_STR_VAL, .off1 = td_var_offset(file_size_low), .off2 = td_var_offset(file_size_high), .minval = 1, .help = "Size of individual files", .interval = 1024 * 1024, .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "offset", .lname = "IO offset", .alias = "fileoffset", .type = FIO_OPT_STR_VAL, .off1 = td_var_offset(start_offset), .help = "Start IO from this offset", .def = "0", .interval = 1024 * 1024, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, { .name = "offset_increment", .lname = "IO offset increment", .type = FIO_OPT_STR_VAL, .off1 = td_var_offset(offset_increment), .help = "What is the increment from one offset to the next", .parent = "offset", .hide = 1, .def = "0", .interval = 1024 * 1024, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, { .name = "number_ios", .lname = "Number of IOs to perform", .type = FIO_OPT_STR_VAL, .off1 = td_var_offset(number_ios), .help = "Force job completion of this number of IOs", .def = "0", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, { .name = "bs", .lname = "Block size", .alias = "blocksize", .type = FIO_OPT_INT, .off1 = td_var_offset(bs[DDIR_READ]), .off2 = td_var_offset(bs[DDIR_WRITE]), .off3 = td_var_offset(bs[DDIR_TRIM]), .minval = 1, .help = "Block size unit", .def = "4k", .parent = "rw", .hide = 1, .interval = 512, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, { .name = "ba", .lname = "Block size align", .alias = "blockalign", .type = FIO_OPT_INT, .off1 = td_var_offset(ba[DDIR_READ]), .off2 = td_var_offset(ba[DDIR_WRITE]), .off3 = td_var_offset(ba[DDIR_TRIM]), .minval = 1, .help = "IO block offset alignment", .parent = "rw", .hide = 1, .interval = 512, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, { .name = "bsrange", .lname = "Block size range", .alias = "blocksize_range", .type = FIO_OPT_RANGE, .off1 = td_var_offset(min_bs[DDIR_READ]), .off2 = td_var_offset(max_bs[DDIR_READ]), .off3 = td_var_offset(min_bs[DDIR_WRITE]), .off4 = td_var_offset(max_bs[DDIR_WRITE]), .off5 = td_var_offset(min_bs[DDIR_TRIM]), .off6 = td_var_offset(max_bs[DDIR_TRIM]), .minval = 1, .help = "Set block size range (in more detail than bs)", .parent = "rw", .hide = 1, .interval = 4096, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, { .name = "bssplit", .lname = "Block size split", .type = FIO_OPT_STR, .cb = str_bssplit_cb, .help = "Set a specific mix of block sizes", .parent = "rw", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, { .name = "bs_unaligned", .lname = "Block size unaligned", .alias = "blocksize_unaligned", .type = FIO_OPT_STR_SET, .off1 = td_var_offset(bs_unaligned), .help = "Don't sector align IO buffer sizes", .parent = "rw", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, { .name = "bs_is_seq_rand", .lname = "Block size division is seq/random (not read/write)", .type = FIO_OPT_BOOL, .off1 = td_var_offset(bs_is_seq_rand), .help = "Consider any blocksize setting to be sequential,ramdom", .def = "0", .parent = "blocksize", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, { .name = "randrepeat", .lname = "Random repeatable", .type = FIO_OPT_BOOL, .off1 = td_var_offset(rand_repeatable), .help = "Use repeatable random IO pattern", .def = "1", .parent = "rw", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, }, { .name = "use_os_rand", .lname = "Use OS random", .type = FIO_OPT_BOOL, .off1 = td_var_offset(use_os_rand), .help = "Set to use OS random generator", .def = "0", .parent = "rw", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, }, { .name = "norandommap", .lname = "No randommap", .type = FIO_OPT_STR_SET, .off1 = td_var_offset(norandommap), .help = "Accept potential duplicate random blocks", .parent = "rw", .hide = 1, .hide_on_set = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, }, { .name = "softrandommap", .lname = "Soft randommap", .type = FIO_OPT_BOOL, .off1 = td_var_offset(softrandommap), .help = "Set norandommap if randommap allocation fails", .parent = "norandommap", .hide = 1, .def = "0", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, }, { .name = "random_generator", .type = FIO_OPT_STR, .off1 = td_var_offset(random_generator), .help = "Type of random number generator to use", .def = "tausworthe", .posval = { { .ival = "tausworthe", .oval = FIO_RAND_GEN_TAUSWORTHE, .help = "Strong Tausworthe generator", }, { .ival = "lfsr", .oval = FIO_RAND_GEN_LFSR, .help = "Variable length LFSR", }, }, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, }, { .name = "random_distribution", .type = FIO_OPT_STR, .off1 = td_var_offset(random_distribution), .cb = str_random_distribution_cb, .help = "Random offset distribution generator", .def = "random", .posval = { { .ival = "random", .oval = FIO_RAND_DIST_RANDOM, .help = "Completely random", }, { .ival = "zipf", .oval = FIO_RAND_DIST_ZIPF, .help = "Zipf distribution", }, { .ival = "pareto", .oval = FIO_RAND_DIST_PARETO, .help = "Pareto distribution", }, }, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, }, { .name = "percentage_random", .lname = "Percentage Random", .type = FIO_OPT_INT, .off1 = td_var_offset(perc_rand[DDIR_READ]), .off2 = td_var_offset(perc_rand[DDIR_WRITE]), .off3 = td_var_offset(perc_rand[DDIR_TRIM]), .maxval = 100, .help = "Percentage of seq/random mix that should be random", .def = "100,100,100", .interval = 5, .inverse = "percentage_sequential", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, }, { .name = "percentage_sequential", .lname = "Percentage Sequential", .type = FIO_OPT_DEPRECATED, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, }, { .name = "nrfiles", .lname = "Number of files", .alias = "nr_files", .type = FIO_OPT_INT, .off1 = td_var_offset(nr_files), .help = "Split job workload between this number of files", .def = "1", .interval = 1, .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "openfiles", .lname = "Number of open files", .type = FIO_OPT_INT, .off1 = td_var_offset(open_files), .help = "Number of files to keep open at the same time", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "file_service_type", .lname = "File service type", .type = FIO_OPT_STR, .cb = str_fst_cb, .off1 = td_var_offset(file_service_type), .help = "How to select which file to service next", .def = "roundrobin", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, .posval = { { .ival = "random", .oval = FIO_FSERVICE_RANDOM, .help = "Choose a file at random", }, { .ival = "roundrobin", .oval = FIO_FSERVICE_RR, .help = "Round robin select files", }, { .ival = "sequential", .oval = FIO_FSERVICE_SEQ, .help = "Finish one file before moving to the next", }, }, .parent = "nrfiles", .hide = 1, }, #ifdef CONFIG_POSIX_FALLOCATE { .name = "fallocate", .lname = "Fallocate", .type = FIO_OPT_STR, .off1 = td_var_offset(fallocate_mode), .help = "Whether pre-allocation is performed when laying out files", .def = "posix", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, .posval = { { .ival = "none", .oval = FIO_FALLOCATE_NONE, .help = "Do not pre-allocate space", }, { .ival = "posix", .oval = FIO_FALLOCATE_POSIX, .help = "Use posix_fallocate()", }, #ifdef CONFIG_LINUX_FALLOCATE { .ival = "keep", .oval = FIO_FALLOCATE_KEEP_SIZE, .help = "Use fallocate(..., FALLOC_FL_KEEP_SIZE, ...)", }, #endif /* Compatibility with former boolean values */ { .ival = "0", .oval = FIO_FALLOCATE_NONE, .help = "Alias for 'none'", }, { .ival = "1", .oval = FIO_FALLOCATE_POSIX, .help = "Alias for 'posix'", }, }, }, #endif /* CONFIG_POSIX_FALLOCATE */ { .name = "fadvise_hint", .lname = "Fadvise hint", .type = FIO_OPT_BOOL, .off1 = td_var_offset(fadvise_hint), .help = "Use fadvise() to advise the kernel on IO pattern", .def = "1", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "fsync", .lname = "Fsync", .type = FIO_OPT_INT, .off1 = td_var_offset(fsync_blocks), .help = "Issue fsync for writes every given number of blocks", .def = "0", .interval = 1, .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "fdatasync", .lname = "Fdatasync", .type = FIO_OPT_INT, .off1 = td_var_offset(fdatasync_blocks), .help = "Issue fdatasync for writes every given number of blocks", .def = "0", .interval = 1, .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "write_barrier", .lname = "Write barrier", .type = FIO_OPT_INT, .off1 = td_var_offset(barrier_blocks), .help = "Make every Nth write a barrier write", .def = "0", .interval = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, #ifdef CONFIG_SYNC_FILE_RANGE { .name = "sync_file_range", .lname = "Sync file range", .posval = { { .ival = "wait_before", .oval = SYNC_FILE_RANGE_WAIT_BEFORE, .help = "SYNC_FILE_RANGE_WAIT_BEFORE", .or = 1, }, { .ival = "write", .oval = SYNC_FILE_RANGE_WRITE, .help = "SYNC_FILE_RANGE_WRITE", .or = 1, }, { .ival = "wait_after", .oval = SYNC_FILE_RANGE_WAIT_AFTER, .help = "SYNC_FILE_RANGE_WAIT_AFTER", .or = 1, }, }, .type = FIO_OPT_STR_MULTI, .cb = str_sfr_cb, .off1 = td_var_offset(sync_file_range), .help = "Use sync_file_range()", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, #endif { .name = "direct", .lname = "Direct I/O", .type = FIO_OPT_BOOL, .off1 = td_var_offset(odirect), .help = "Use O_DIRECT IO (negates buffered)", .def = "0", .inverse = "buffered", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_TYPE, }, { .name = "buffered", .lname = "Buffered I/O", .type = FIO_OPT_BOOL, .off1 = td_var_offset(odirect), .neg = 1, .help = "Use buffered IO (negates direct)", .def = "1", .inverse = "direct", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_TYPE, }, { .name = "overwrite", .lname = "Overwrite", .type = FIO_OPT_BOOL, .off1 = td_var_offset(overwrite), .help = "When writing, set whether to overwrite current data", .def = "0", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "loops", .lname = "Loops", .type = FIO_OPT_INT, .off1 = td_var_offset(loops), .help = "Number of times to run the job", .def = "1", .interval = 1, .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_RUNTIME, }, { .name = "numjobs", .lname = "Number of jobs", .type = FIO_OPT_INT, .off1 = td_var_offset(numjobs), .help = "Duplicate this job this many times", .def = "1", .interval = 1, .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_RUNTIME, }, { .name = "startdelay", .lname = "Start delay", .type = FIO_OPT_STR_VAL_TIME, .off1 = td_var_offset(start_delay), .help = "Only start job when this period has passed", .def = "0", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_RUNTIME, }, { .name = "runtime", .lname = "Runtime", .alias = "timeout", .type = FIO_OPT_STR_VAL_TIME, .off1 = td_var_offset(timeout), .help = "Stop workload when this amount of time has passed", .def = "0", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_RUNTIME, }, { .name = "time_based", .lname = "Time based", .type = FIO_OPT_STR_SET, .off1 = td_var_offset(time_based), .help = "Keep running until runtime/timeout is met", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_RUNTIME, }, { .name = "ramp_time", .lname = "Ramp time", .type = FIO_OPT_STR_VAL_TIME, .off1 = td_var_offset(ramp_time), .help = "Ramp up time before measuring performance", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_RUNTIME, }, { .name = "clocksource", .lname = "Clock source", .type = FIO_OPT_STR, .cb = fio_clock_source_cb, .off1 = td_var_offset(clocksource), .help = "What type of timing source to use", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CLOCK, .posval = { #ifdef CONFIG_GETTIMEOFDAY { .ival = "gettimeofday", .oval = CS_GTOD, .help = "Use gettimeofday(2) for timing", }, #endif #ifdef CONFIG_CLOCK_GETTIME { .ival = "clock_gettime", .oval = CS_CGETTIME, .help = "Use clock_gettime(2) for timing", }, #endif #ifdef ARCH_HAVE_CPU_CLOCK { .ival = "cpu", .oval = CS_CPUCLOCK, .help = "Use CPU private clock", }, #endif }, }, { .name = "mem", .alias = "iomem", .lname = "I/O Memory", .type = FIO_OPT_STR, .cb = str_mem_cb, .off1 = td_var_offset(mem_type), .help = "Backing type for IO buffers", .def = "malloc", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, .posval = { { .ival = "malloc", .oval = MEM_MALLOC, .help = "Use malloc(3) for IO buffers", }, { .ival = "shm", .oval = MEM_SHM, .help = "Use shared memory segments for IO buffers", }, #ifdef FIO_HAVE_HUGETLB { .ival = "shmhuge", .oval = MEM_SHMHUGE, .help = "Like shm, but use huge pages", }, #endif { .ival = "mmap", .oval = MEM_MMAP, .help = "Use mmap(2) (file or anon) for IO buffers", }, #ifdef FIO_HAVE_HUGETLB { .ival = "mmaphuge", .oval = MEM_MMAPHUGE, .help = "Like mmap, but use huge pages", }, #endif }, }, { .name = "iomem_align", .alias = "mem_align", .lname = "I/O memory alignment", .type = FIO_OPT_INT, .off1 = td_var_offset(mem_align), .minval = 0, .help = "IO memory buffer offset alignment", .def = "0", .parent = "iomem", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, { .name = "verify", .lname = "Verify", .type = FIO_OPT_STR, .off1 = td_var_offset(verify), .help = "Verify data written", .def = "0", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, .posval = { { .ival = "0", .oval = VERIFY_NONE, .help = "Don't do IO verification", }, { .ival = "md5", .oval = VERIFY_MD5, .help = "Use md5 checksums for verification", }, { .ival = "crc64", .oval = VERIFY_CRC64, .help = "Use crc64 checksums for verification", }, { .ival = "crc32", .oval = VERIFY_CRC32, .help = "Use crc32 checksums for verification", }, { .ival = "crc32c-intel", .oval = VERIFY_CRC32C, .help = "Use crc32c checksums for verification (hw assisted, if available)", }, { .ival = "crc32c", .oval = VERIFY_CRC32C, .help = "Use crc32c checksums for verification (hw assisted, if available)", }, { .ival = "crc16", .oval = VERIFY_CRC16, .help = "Use crc16 checksums for verification", }, { .ival = "crc7", .oval = VERIFY_CRC7, .help = "Use crc7 checksums for verification", }, { .ival = "sha1", .oval = VERIFY_SHA1, .help = "Use sha1 checksums for verification", }, { .ival = "sha256", .oval = VERIFY_SHA256, .help = "Use sha256 checksums for verification", }, { .ival = "sha512", .oval = VERIFY_SHA512, .help = "Use sha512 checksums for verification", }, { .ival = "meta", .oval = VERIFY_META, .help = "Use io information", }, { .ival = "null", .oval = VERIFY_NULL, .help = "Pretend to verify", }, }, }, { .name = "do_verify", .lname = "Perform verify step", .type = FIO_OPT_BOOL, .off1 = td_var_offset(do_verify), .help = "Run verification stage after write", .def = "1", .parent = "verify", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, { .name = "verifysort", .lname = "Verify sort", .type = FIO_OPT_BOOL, .off1 = td_var_offset(verifysort), .help = "Sort written verify blocks for read back", .def = "1", .parent = "verify", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, { .name = "verifysort_nr", .type = FIO_OPT_INT, .off1 = td_var_offset(verifysort_nr), .help = "Pre-load and sort verify blocks for a read workload", .minval = 0, .maxval = 131072, .def = "1024", .parent = "verify", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, { .name = "verify_interval", .lname = "Verify interval", .type = FIO_OPT_INT, .off1 = td_var_offset(verify_interval), .minval = 2 * sizeof(struct verify_header), .help = "Store verify buffer header every N bytes", .parent = "verify", .hide = 1, .interval = 2 * sizeof(struct verify_header), .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, { .name = "verify_offset", .lname = "Verify offset", .type = FIO_OPT_INT, .help = "Offset verify header location by N bytes", .off1 = td_var_offset(verify_offset), .minval = sizeof(struct verify_header), .parent = "verify", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, { .name = "verify_pattern", .lname = "Verify pattern", .type = FIO_OPT_STR, .cb = str_verify_pattern_cb, .help = "Fill pattern for IO buffers", .parent = "verify", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, { .name = "verify_fatal", .lname = "Verify fatal", .type = FIO_OPT_BOOL, .off1 = td_var_offset(verify_fatal), .def = "0", .help = "Exit on a single verify failure, don't continue", .parent = "verify", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, { .name = "verify_dump", .lname = "Verify dump", .type = FIO_OPT_BOOL, .off1 = td_var_offset(verify_dump), .def = "0", .help = "Dump contents of good and bad blocks on failure", .parent = "verify", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, { .name = "verify_async", .lname = "Verify asynchronously", .type = FIO_OPT_INT, .off1 = td_var_offset(verify_async), .def = "0", .help = "Number of async verifier threads to use", .parent = "verify", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, { .name = "verify_backlog", .lname = "Verify backlog", .type = FIO_OPT_STR_VAL, .off1 = td_var_offset(verify_backlog), .help = "Verify after this number of blocks are written", .parent = "verify", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, { .name = "verify_backlog_batch", .lname = "Verify backlog batch", .type = FIO_OPT_INT, .off1 = td_var_offset(verify_batch), .help = "Verify this number of IO blocks", .parent = "verify", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, #ifdef FIO_HAVE_CPU_AFFINITY { .name = "verify_async_cpus", .lname = "Async verify CPUs", .type = FIO_OPT_STR, .cb = str_verify_cpus_allowed_cb, .help = "Set CPUs allowed for async verify threads", .parent = "verify_async", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, #endif { .name = "experimental_verify", .off1 = td_var_offset(experimental_verify), .type = FIO_OPT_BOOL, .help = "Enable experimental verification", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_VERIFY, }, #ifdef FIO_HAVE_TRIM { .name = "trim_percentage", .lname = "Trim percentage", .type = FIO_OPT_INT, .off1 = td_var_offset(trim_percentage), .minval = 0, .maxval = 100, .help = "Number of verify blocks to discard/trim", .parent = "verify", .def = "0", .interval = 1, .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_TRIM, }, { .name = "trim_verify_zero", .lname = "Verify trim zero", .type = FIO_OPT_BOOL, .help = "Verify that trim/discarded blocks are returned as zeroes", .off1 = td_var_offset(trim_zero), .parent = "trim_percentage", .hide = 1, .def = "1", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_TRIM, }, { .name = "trim_backlog", .lname = "Trim backlog", .type = FIO_OPT_STR_VAL, .off1 = td_var_offset(trim_backlog), .help = "Trim after this number of blocks are written", .parent = "trim_percentage", .hide = 1, .interval = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_TRIM, }, { .name = "trim_backlog_batch", .lname = "Trim backlog batch", .type = FIO_OPT_INT, .off1 = td_var_offset(trim_batch), .help = "Trim this number of IO blocks", .parent = "trim_percentage", .hide = 1, .interval = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_TRIM, }, #endif { .name = "write_iolog", .lname = "Write I/O log", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(write_iolog_file), .help = "Store IO pattern to file", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IOLOG, }, { .name = "read_iolog", .lname = "Read I/O log", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(read_iolog_file), .help = "Playback IO pattern from file", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IOLOG, }, { .name = "replay_no_stall", .lname = "Don't stall on replay", .type = FIO_OPT_BOOL, .off1 = td_var_offset(no_stall), .def = "0", .parent = "read_iolog", .hide = 1, .help = "Playback IO pattern file as fast as possible without stalls", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IOLOG, }, { .name = "replay_redirect", .lname = "Redirect device for replay", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(replay_redirect), .parent = "read_iolog", .hide = 1, .help = "Replay all I/O onto this device, regardless of trace device", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IOLOG, }, { .name = "exec_prerun", .lname = "Pre-execute runnable", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(exec_prerun), .help = "Execute this file prior to running job", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_INVALID, }, { .name = "exec_postrun", .lname = "Post-execute runnable", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(exec_postrun), .help = "Execute this file after running job", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_INVALID, }, #ifdef FIO_HAVE_IOSCHED_SWITCH { .name = "ioscheduler", .lname = "I/O scheduler", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(ioscheduler), .help = "Use this IO scheduler on the backing device", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, #endif { .name = "zonesize", .lname = "Zone size", .type = FIO_OPT_STR_VAL, .off1 = td_var_offset(zone_size), .help = "Amount of data to read per zone", .def = "0", .interval = 1024 * 1024, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_ZONE, }, { .name = "zonerange", .lname = "Zone range", .type = FIO_OPT_STR_VAL, .off1 = td_var_offset(zone_range), .help = "Give size of an IO zone", .def = "0", .interval = 1024 * 1024, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_ZONE, }, { .name = "zoneskip", .lname = "Zone skip", .type = FIO_OPT_STR_VAL, .off1 = td_var_offset(zone_skip), .help = "Space between IO zones", .def = "0", .interval = 1024 * 1024, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_ZONE, }, { .name = "lockmem", .lname = "Lock memory", .type = FIO_OPT_STR_VAL, .off1 = td_var_offset(lockmem), .help = "Lock down this amount of memory (per worker)", .def = "0", .interval = 1024 * 1024, .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_INVALID, }, { .name = "rwmixread", .lname = "Read/write mix read", .type = FIO_OPT_INT, .cb = str_rwmix_read_cb, .maxval = 100, .help = "Percentage of mixed workload that is reads", .def = "50", .interval = 5, .inverse = "rwmixwrite", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RWMIX, }, { .name = "rwmixwrite", .lname = "Read/write mix write", .type = FIO_OPT_INT, .cb = str_rwmix_write_cb, .maxval = 100, .help = "Percentage of mixed workload that is writes", .def = "50", .interval = 5, .inverse = "rwmixread", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RWMIX, }, { .name = "rwmixcycle", .lname = "Read/write mix cycle", .type = FIO_OPT_DEPRECATED, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RWMIX, }, { .name = "nice", .lname = "Nice", .type = FIO_OPT_INT, .off1 = td_var_offset(nice), .help = "Set job CPU nice value", .minval = -19, .maxval = 20, .def = "0", .interval = 1, .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CRED, }, #ifdef FIO_HAVE_IOPRIO { .name = "prio", .lname = "I/O nice priority", .type = FIO_OPT_INT, .off1 = td_var_offset(ioprio), .help = "Set job IO priority value", .minval = 0, .maxval = 7, .interval = 1, .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CRED, }, { .name = "prioclass", .lname = "I/O nice priority class", .type = FIO_OPT_INT, .off1 = td_var_offset(ioprio_class), .help = "Set job IO priority class", .minval = 0, .maxval = 3, .interval = 1, .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CRED, }, #endif { .name = "thinktime", .lname = "Thinktime", .type = FIO_OPT_INT, .off1 = td_var_offset(thinktime), .help = "Idle time between IO buffers (usec)", .def = "0", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_THINKTIME, }, { .name = "thinktime_spin", .lname = "Thinktime spin", .type = FIO_OPT_INT, .off1 = td_var_offset(thinktime_spin), .help = "Start think time by spinning this amount (usec)", .def = "0", .parent = "thinktime", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_THINKTIME, }, { .name = "thinktime_blocks", .lname = "Thinktime blocks", .type = FIO_OPT_INT, .off1 = td_var_offset(thinktime_blocks), .help = "IO buffer period between 'thinktime'", .def = "1", .parent = "thinktime", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_THINKTIME, }, { .name = "rate", .lname = "I/O rate", .type = FIO_OPT_INT, .off1 = td_var_offset(rate[DDIR_READ]), .off2 = td_var_offset(rate[DDIR_WRITE]), .off3 = td_var_offset(rate[DDIR_TRIM]), .help = "Set bandwidth rate", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RATE, }, { .name = "ratemin", .lname = "I/O min rate", .type = FIO_OPT_INT, .off1 = td_var_offset(ratemin[DDIR_READ]), .off2 = td_var_offset(ratemin[DDIR_WRITE]), .off3 = td_var_offset(ratemin[DDIR_TRIM]), .help = "Job must meet this rate or it will be shutdown", .parent = "rate", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RATE, }, { .name = "rate_iops", .lname = "I/O rate IOPS", .type = FIO_OPT_INT, .off1 = td_var_offset(rate_iops[DDIR_READ]), .off2 = td_var_offset(rate_iops[DDIR_WRITE]), .off3 = td_var_offset(rate_iops[DDIR_TRIM]), .help = "Limit IO used to this number of IO operations/sec", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RATE, }, { .name = "rate_iops_min", .lname = "I/O min rate IOPS", .type = FIO_OPT_INT, .off1 = td_var_offset(rate_iops_min[DDIR_READ]), .off2 = td_var_offset(rate_iops_min[DDIR_WRITE]), .off3 = td_var_offset(rate_iops_min[DDIR_TRIM]), .help = "Job must meet this rate or it will be shut down", .parent = "rate_iops", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RATE, }, { .name = "ratecycle", .lname = "I/O rate cycle", .type = FIO_OPT_INT, .off1 = td_var_offset(ratecycle), .help = "Window average for rate limits (msec)", .def = "1000", .parent = "rate", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RATE, }, { .name = "max_latency", .type = FIO_OPT_INT, .off1 = td_var_offset(max_latency), .help = "Maximum tolerated IO latency (usec)", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RATE, }, { .name = "invalidate", .lname = "Cache invalidate", .type = FIO_OPT_BOOL, .off1 = td_var_offset(invalidate_cache), .help = "Invalidate buffer/page cache prior to running job", .def = "1", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_TYPE, }, { .name = "sync", .lname = "Synchronous I/O", .type = FIO_OPT_BOOL, .off1 = td_var_offset(sync_io), .help = "Use O_SYNC for buffered writes", .def = "0", .parent = "buffered", .hide = 1, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_TYPE, }, { .name = "create_serialize", .lname = "Create serialize", .type = FIO_OPT_BOOL, .off1 = td_var_offset(create_serialize), .help = "Serialize creating of job files", .def = "1", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "create_fsync", .lname = "Create fsync", .type = FIO_OPT_BOOL, .off1 = td_var_offset(create_fsync), .help = "fsync file after creation", .def = "1", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "create_on_open", .lname = "Create on open", .type = FIO_OPT_BOOL, .off1 = td_var_offset(create_on_open), .help = "Create files when they are opened for IO", .def = "0", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "create_only", .type = FIO_OPT_BOOL, .off1 = td_var_offset(create_only), .help = "Only perform file creation phase", .category = FIO_OPT_C_FILE, .def = "0", }, { .name = "pre_read", .lname = "Pre-read files", .type = FIO_OPT_BOOL, .off1 = td_var_offset(pre_read), .help = "Pre-read files before starting official testing", .def = "0", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, #ifdef FIO_HAVE_CPU_AFFINITY { .name = "cpumask", .lname = "CPU mask", .type = FIO_OPT_INT, .cb = str_cpumask_cb, .help = "CPU affinity mask", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CRED, }, { .name = "cpus_allowed", .lname = "CPUs allowed", .type = FIO_OPT_STR, .cb = str_cpus_allowed_cb, .help = "Set CPUs allowed", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CRED, }, #endif #ifdef CONFIG_LIBNUMA { .name = "numa_cpu_nodes", .type = FIO_OPT_STR, .cb = str_numa_cpunodes_cb, .help = "NUMA CPU nodes bind", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_INVALID, }, { .name = "numa_mem_policy", .type = FIO_OPT_STR, .cb = str_numa_mpol_cb, .help = "NUMA memory policy setup", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_INVALID, }, #endif { .name = "end_fsync", .lname = "End fsync", .type = FIO_OPT_BOOL, .off1 = td_var_offset(end_fsync), .help = "Include fsync at the end of job", .def = "0", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "fsync_on_close", .lname = "Fsync on close", .type = FIO_OPT_BOOL, .off1 = td_var_offset(fsync_on_close), .help = "fsync files on close", .def = "0", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "unlink", .lname = "Unlink file", .type = FIO_OPT_BOOL, .off1 = td_var_offset(unlink), .help = "Unlink created files after job has completed", .def = "0", .category = FIO_OPT_C_FILE, .group = FIO_OPT_G_INVALID, }, { .name = "exitall", .lname = "Exit-all on terminate", .type = FIO_OPT_STR_SET, .cb = str_exitall_cb, .help = "Terminate all jobs when one exits", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_PROCESS, }, { .name = "stonewall", .lname = "Wait for previous", .alias = "wait_for_previous", .type = FIO_OPT_STR_SET, .off1 = td_var_offset(stonewall), .help = "Insert a hard barrier between this job and previous", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_PROCESS, }, { .name = "new_group", .lname = "New group", .type = FIO_OPT_STR_SET, .off1 = td_var_offset(new_group), .help = "Mark the start of a new group (for reporting)", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_PROCESS, }, { .name = "thread", .lname = "Thread", .type = FIO_OPT_STR_SET, .off1 = td_var_offset(use_thread), .help = "Use threads instead of processes", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_PROCESS, }, { .name = "write_bw_log", .lname = "Write bandwidth log", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(bw_log_file), .help = "Write log of bandwidth during run", .category = FIO_OPT_C_LOG, .group = FIO_OPT_G_INVALID, }, { .name = "write_lat_log", .lname = "Write latency log", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(lat_log_file), .help = "Write log of latency during run", .category = FIO_OPT_C_LOG, .group = FIO_OPT_G_INVALID, }, { .name = "write_iops_log", .lname = "Write IOPS log", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(iops_log_file), .help = "Write log of IOPS during run", .category = FIO_OPT_C_LOG, .group = FIO_OPT_G_INVALID, }, { .name = "log_avg_msec", .lname = "Log averaging (msec)", .type = FIO_OPT_INT, .off1 = td_var_offset(log_avg_msec), .help = "Average bw/iops/lat logs over this period of time", .def = "0", .category = FIO_OPT_C_LOG, .group = FIO_OPT_G_INVALID, }, { .name = "bwavgtime", .lname = "Bandwidth average time", .type = FIO_OPT_INT, .off1 = td_var_offset(bw_avg_time), .help = "Time window over which to calculate bandwidth" " (msec)", .def = "500", .parent = "write_bw_log", .hide = 1, .interval = 100, .category = FIO_OPT_C_LOG, .group = FIO_OPT_G_INVALID, }, { .name = "iopsavgtime", .lname = "IOPS average time", .type = FIO_OPT_INT, .off1 = td_var_offset(iops_avg_time), .help = "Time window over which to calculate IOPS (msec)", .def = "500", .parent = "write_iops_log", .hide = 1, .interval = 100, .category = FIO_OPT_C_LOG, .group = FIO_OPT_G_INVALID, }, { .name = "group_reporting", .lname = "Group reporting", .type = FIO_OPT_STR_SET, .off1 = td_var_offset(group_reporting), .help = "Do reporting on a per-group basis", .category = FIO_OPT_C_STAT, .group = FIO_OPT_G_INVALID, }, { .name = "zero_buffers", .lname = "Zero I/O buffers", .type = FIO_OPT_STR_SET, .off1 = td_var_offset(zero_buffers), .help = "Init IO buffers to all zeroes", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BUF, }, { .name = "refill_buffers", .lname = "Refill I/O buffers", .type = FIO_OPT_STR_SET, .off1 = td_var_offset(refill_buffers), .help = "Refill IO buffers on every IO submit", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BUF, }, { .name = "scramble_buffers", .lname = "Scramble I/O buffers", .type = FIO_OPT_BOOL, .off1 = td_var_offset(scramble_buffers), .help = "Slightly scramble buffers on every IO submit", .def = "1", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BUF, }, { .name = "buffer_compress_percentage", .lname = "Buffer compression percentage", .type = FIO_OPT_INT, .off1 = td_var_offset(compress_percentage), .maxval = 100, .minval = 1, .help = "How compressible the buffer is (approximately)", .interval = 5, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BUF, }, { .name = "buffer_compress_chunk", .lname = "Buffer compression chunk size", .type = FIO_OPT_INT, .off1 = td_var_offset(compress_chunk), .parent = "buffer_compress_percentage", .hide = 1, .help = "Size of compressible region in buffer", .interval = 256, .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BUF, }, { .name = "clat_percentiles", .lname = "Completion latency percentiles", .type = FIO_OPT_BOOL, .off1 = td_var_offset(clat_percentiles), .help = "Enable the reporting of completion latency percentiles", .def = "1", .category = FIO_OPT_C_STAT, .group = FIO_OPT_G_INVALID, }, { .name = "percentile_list", .lname = "Completion latency percentile list", .type = FIO_OPT_FLOAT_LIST, .off1 = td_var_offset(percentile_list), .off2 = td_var_offset(percentile_precision), .help = "Specify a custom list of percentiles to report", .def = "1:5:10:20:30:40:50:60:70:80:90:95:99:99.5:99.9:99.95:99.99", .maxlen = FIO_IO_U_LIST_MAX_LEN, .minfp = 0.0, .maxfp = 100.0, .category = FIO_OPT_C_STAT, .group = FIO_OPT_G_INVALID, }, #ifdef FIO_HAVE_DISK_UTIL { .name = "disk_util", .lname = "Disk utilization", .type = FIO_OPT_BOOL, .off1 = td_var_offset(do_disk_util), .help = "Log disk utilization statistics", .def = "1", .category = FIO_OPT_C_STAT, .group = FIO_OPT_G_INVALID, }, #endif { .name = "gtod_reduce", .lname = "Reduce gettimeofday() calls", .type = FIO_OPT_BOOL, .help = "Greatly reduce number of gettimeofday() calls", .cb = str_gtod_reduce_cb, .def = "0", .hide_on_set = 1, .category = FIO_OPT_C_STAT, .group = FIO_OPT_G_INVALID, }, { .name = "disable_lat", .lname = "Disable all latency stats", .type = FIO_OPT_BOOL, .off1 = td_var_offset(disable_lat), .help = "Disable latency numbers", .parent = "gtod_reduce", .hide = 1, .def = "0", .category = FIO_OPT_C_STAT, .group = FIO_OPT_G_INVALID, }, { .name = "disable_clat", .lname = "Disable completion latency stats", .type = FIO_OPT_BOOL, .off1 = td_var_offset(disable_clat), .help = "Disable completion latency numbers", .parent = "gtod_reduce", .hide = 1, .def = "0", .category = FIO_OPT_C_STAT, .group = FIO_OPT_G_INVALID, }, { .name = "disable_slat", .lname = "Disable submission latency stats", .type = FIO_OPT_BOOL, .off1 = td_var_offset(disable_slat), .help = "Disable submission latency numbers", .parent = "gtod_reduce", .hide = 1, .def = "0", .category = FIO_OPT_C_STAT, .group = FIO_OPT_G_INVALID, }, { .name = "disable_bw_measurement", .lname = "Disable bandwidth stats", .type = FIO_OPT_BOOL, .off1 = td_var_offset(disable_bw), .help = "Disable bandwidth logging", .parent = "gtod_reduce", .hide = 1, .def = "0", .category = FIO_OPT_C_STAT, .group = FIO_OPT_G_INVALID, }, { .name = "gtod_cpu", .lname = "Dedicated gettimeofday() CPU", .type = FIO_OPT_INT, .cb = str_gtod_cpu_cb, .help = "Set up dedicated gettimeofday() thread on this CPU", .verify = gtod_cpu_verify, .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CLOCK, }, { .name = "unified_rw_reporting", .type = FIO_OPT_BOOL, .off1 = td_var_offset(unified_rw_rep), .help = "Unify reporting across data direction", .def = "0", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_INVALID, }, { .name = "continue_on_error", .lname = "Continue on error", .type = FIO_OPT_STR, .off1 = td_var_offset(continue_on_error), .help = "Continue on non-fatal errors during IO", .def = "none", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_ERR, .posval = { { .ival = "none", .oval = ERROR_TYPE_NONE, .help = "Exit when an error is encountered", }, { .ival = "read", .oval = ERROR_TYPE_READ, .help = "Continue on read errors only", }, { .ival = "write", .oval = ERROR_TYPE_WRITE, .help = "Continue on write errors only", }, { .ival = "io", .oval = ERROR_TYPE_READ | ERROR_TYPE_WRITE, .help = "Continue on any IO errors", }, { .ival = "verify", .oval = ERROR_TYPE_VERIFY, .help = "Continue on verify errors only", }, { .ival = "all", .oval = ERROR_TYPE_ANY, .help = "Continue on all io and verify errors", }, { .ival = "0", .oval = ERROR_TYPE_NONE, .help = "Alias for 'none'", }, { .ival = "1", .oval = ERROR_TYPE_ANY, .help = "Alias for 'all'", }, }, }, { .name = "ignore_error", .type = FIO_OPT_STR, .cb = str_ignore_error_cb, .help = "Set a specific list of errors to ignore", .parent = "rw", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_ERR, }, { .name = "error_dump", .type = FIO_OPT_BOOL, .off1 = td_var_offset(error_dump), .def = "0", .help = "Dump info on each error", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_ERR, }, { .name = "profile", .lname = "Profile", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(profile), .help = "Select a specific builtin performance test", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_INVALID, }, { .name = "cgroup", .lname = "Cgroup", .type = FIO_OPT_STR_STORE, .off1 = td_var_offset(cgroup), .help = "Add job to cgroup of this name", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CGROUP, }, { .name = "cgroup_nodelete", .lname = "Cgroup no-delete", .type = FIO_OPT_BOOL, .off1 = td_var_offset(cgroup_nodelete), .help = "Do not delete cgroups after job completion", .def = "0", .parent = "cgroup", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CGROUP, }, { .name = "cgroup_weight", .lname = "Cgroup weight", .type = FIO_OPT_INT, .off1 = td_var_offset(cgroup_weight), .help = "Use given weight for cgroup", .minval = 100, .maxval = 1000, .parent = "cgroup", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CGROUP, }, { .name = "uid", .lname = "User ID", .type = FIO_OPT_INT, .off1 = td_var_offset(uid), .help = "Run job with this user ID", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CRED, }, { .name = "gid", .lname = "Group ID", .type = FIO_OPT_INT, .off1 = td_var_offset(gid), .help = "Run job with this group ID", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CRED, }, { .name = "kb_base", .lname = "KB Base", .type = FIO_OPT_INT, .off1 = td_var_offset(kb_base), .prio = 1, .def = "1024", .posval = { { .ival = "1024", .oval = 1024, .help = "Use 1024 as the K base", }, { .ival = "1000", .oval = 1000, .help = "Use 1000 as the K base", }, }, .help = "How many bytes per KB for reporting (1000 or 1024)", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_INVALID, }, { .name = "unit_base", .lname = "Base unit for reporting (Bits or Bytes)", .type = FIO_OPT_INT, .off1 = td_var_offset(unit_base), .prio = 1, .posval = { { .ival = "0", .oval = 0, .help = "Auto-detect", }, { .ival = "8", .oval = 8, .help = "Normal (byte based)", }, { .ival = "1", .oval = 1, .help = "Bit based", }, }, .help = "Bit multiple of result summary data (8 for byte, 1 for bit)", .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_INVALID, }, { .name = "hugepage-size", .lname = "Hugepage size", .type = FIO_OPT_INT, .off1 = td_var_offset(hugepage_size), .help = "When using hugepages, specify size of each page", .def = __fio_stringify(FIO_HUGE_PAGE), .interval = 1024 * 1024, .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_INVALID, }, { .name = "flow_id", .lname = "I/O flow ID", .type = FIO_OPT_INT, .off1 = td_var_offset(flow_id), .help = "The flow index ID to use", .def = "0", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_FLOW, }, { .name = "flow", .lname = "I/O flow weight", .type = FIO_OPT_INT, .off1 = td_var_offset(flow), .help = "Weight for flow control of this job", .parent = "flow_id", .hide = 1, .def = "0", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_FLOW, }, { .name = "flow_watermark", .lname = "I/O flow watermark", .type = FIO_OPT_INT, .off1 = td_var_offset(flow_watermark), .help = "High watermark for flow control. This option" " should be set to the same value for all threads" " with non-zero flow.", .parent = "flow_id", .hide = 1, .def = "1024", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_FLOW, }, { .name = "flow_sleep", .lname = "I/O flow sleep", .type = FIO_OPT_INT, .off1 = td_var_offset(flow_sleep), .help = "How many microseconds to sleep after being held" " back by the flow control mechanism", .parent = "flow_id", .hide = 1, .def = "0", .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_FLOW, }, { .name = NULL, }, }; static void add_to_lopt(struct option *lopt, struct fio_option *o, const char *name, int val) { lopt->name = (char *) name; lopt->val = val; if (o->type == FIO_OPT_STR_SET) lopt->has_arg = no_argument; else lopt->has_arg = required_argument; } static void options_to_lopts(struct fio_option *opts, struct option *long_options, int i, int option_type) { struct fio_option *o = &opts[0]; while (o->name) { add_to_lopt(&long_options[i], o, o->name, option_type); if (o->alias) { i++; add_to_lopt(&long_options[i], o, o->alias, option_type); } i++; o++; assert(i < FIO_NR_OPTIONS); } } void fio_options_set_ioengine_opts(struct option *long_options, struct thread_data *td) { unsigned int i; i = 0; while (long_options[i].name) { if (long_options[i].val == FIO_GETOPT_IOENGINE) { memset(&long_options[i], 0, sizeof(*long_options)); break; } i++; } /* * Just clear out the prior ioengine options. */ if (!td || !td->eo) return; options_to_lopts(td->io_ops->options, long_options, i, FIO_GETOPT_IOENGINE); } void fio_options_dup_and_init(struct option *long_options) { unsigned int i; options_init(fio_options); i = 0; while (long_options[i].name) i++; options_to_lopts(fio_options, long_options, i, FIO_GETOPT_JOB); } struct fio_keyword { const char *word; const char *desc; char *replace; }; static struct fio_keyword fio_keywords[] = { { .word = "$pagesize", .desc = "Page size in the system", }, { .word = "$mb_memory", .desc = "Megabytes of memory online", }, { .word = "$ncpus", .desc = "Number of CPUs online in the system", }, { .word = NULL, }, }; void fio_keywords_init(void) { unsigned long long mb_memory; char buf[128]; long l; sprintf(buf, "%lu", (unsigned long) page_size); fio_keywords[0].replace = strdup(buf); mb_memory = os_phys_mem() / (1024 * 1024); sprintf(buf, "%llu", mb_memory); fio_keywords[1].replace = strdup(buf); l = cpus_online(); sprintf(buf, "%lu", l); fio_keywords[2].replace = strdup(buf); } #define BC_APP "bc" static char *bc_calc(char *str) { char buf[128], *tmp; FILE *f; int ret; /* * No math, just return string */ if ((!strchr(str, '+') && !strchr(str, '-') && !strchr(str, '*') && !strchr(str, '/')) || strchr(str, '\'')) return str; /* * Split option from value, we only need to calculate the value */ tmp = strchr(str, '='); if (!tmp) return str; tmp++; /* * Prevent buffer overflows; such a case isn't reasonable anyway */ if (strlen(str) >= 128 || strlen(tmp) > 100) return str; sprintf(buf, "which %s > /dev/null", BC_APP); if (system(buf)) { log_err("fio: bc is needed for performing math\n"); return NULL; } sprintf(buf, "echo '%s' | %s", tmp, BC_APP); f = popen(buf, "r"); if (!f) return NULL; ret = fread(&buf[tmp - str], 1, 128 - (tmp - str), f); if (ret <= 0) return NULL; pclose(f); buf[(tmp - str) + ret - 1] = '\0'; memcpy(buf, str, tmp - str); free(str); return strdup(buf); } /* * Return a copy of the input string with substrings of the form ${VARNAME} * substituted with the value of the environment variable VARNAME. The * substitution always occurs, even if VARNAME is empty or the corresponding * environment variable undefined. */ static char *option_dup_subs(const char *opt) { char out[OPT_LEN_MAX+1]; char in[OPT_LEN_MAX+1]; char *outptr = out; char *inptr = in; char *ch1, *ch2, *env; ssize_t nchr = OPT_LEN_MAX; size_t envlen; if (strlen(opt) + 1 > OPT_LEN_MAX) { log_err("OPT_LEN_MAX (%d) is too small\n", OPT_LEN_MAX); return NULL; } in[OPT_LEN_MAX] = '\0'; strncpy(in, opt, OPT_LEN_MAX); while (*inptr && nchr > 0) { if (inptr[0] == '$' && inptr[1] == '{') { ch2 = strchr(inptr, '}'); if (ch2 && inptr+1 < ch2) { ch1 = inptr+2; inptr = ch2+1; *ch2 = '\0'; env = getenv(ch1); if (env) { envlen = strlen(env); if (envlen <= nchr) { memcpy(outptr, env, envlen); outptr += envlen; nchr -= envlen; } } continue; } } *outptr++ = *inptr++; --nchr; } *outptr = '\0'; return strdup(out); } /* * Look for reserved variable names and replace them with real values */ static char *fio_keyword_replace(char *opt) { char *s; int i; int docalc = 0; for (i = 0; fio_keywords[i].word != NULL; i++) { struct fio_keyword *kw = &fio_keywords[i]; while ((s = strstr(opt, kw->word)) != NULL) { char *new = malloc(strlen(opt) + 1); char *o_org = opt; int olen = s - opt; int len; /* * Copy part of the string before the keyword and * sprintf() the replacement after it. */ memcpy(new, opt, olen); len = sprintf(new + olen, "%s", kw->replace); /* * If there's more in the original string, copy that * in too */ opt += strlen(kw->word) + olen; if (strlen(opt)) memcpy(new + olen + len, opt, opt - o_org - 1); /* * replace opt and free the old opt */ opt = new; free(o_org); docalc = 1; } } /* * Check for potential math and invoke bc, if possible */ if (docalc) opt = bc_calc(opt); return opt; } static char **dup_and_sub_options(char **opts, int num_opts) { int i; char **opts_copy = malloc(num_opts * sizeof(*opts)); for (i = 0; i < num_opts; i++) { opts_copy[i] = option_dup_subs(opts[i]); if (!opts_copy[i]) continue; opts_copy[i] = fio_keyword_replace(opts_copy[i]); } return opts_copy; } int fio_options_parse(struct thread_data *td, char **opts, int num_opts) { int i, ret, unknown; char **opts_copy; sort_options(opts, fio_options, num_opts); opts_copy = dup_and_sub_options(opts, num_opts); for (ret = 0, i = 0, unknown = 0; i < num_opts; i++) { struct fio_option *o; int newret = parse_option(opts_copy[i], opts[i], fio_options, &o, td); if (opts_copy[i]) { if (newret && !o) { unknown++; continue; } free(opts_copy[i]); opts_copy[i] = NULL; } ret |= newret; } if (unknown) { ret |= ioengine_load(td); if (td->eo) { sort_options(opts_copy, td->io_ops->options, num_opts); opts = opts_copy; } for (i = 0; i < num_opts; i++) { struct fio_option *o = NULL; int newret = 1; if (!opts_copy[i]) continue; if (td->eo) newret = parse_option(opts_copy[i], opts[i], td->io_ops->options, &o, td->eo); ret |= newret; if (!o) log_err("Bad option <%s>\n", opts[i]); free(opts_copy[i]); opts_copy[i] = NULL; } } free(opts_copy); return ret; } int fio_cmd_option_parse(struct thread_data *td, const char *opt, char *val) { return parse_cmd_option(opt, val, fio_options, td); } int fio_cmd_ioengine_option_parse(struct thread_data *td, const char *opt, char *val) { return parse_cmd_option(opt, val, td->io_ops->options, td); } void fio_fill_default_options(struct thread_data *td) { fill_default_options(td, fio_options); } int fio_show_option_help(const char *opt) { return show_cmd_help(fio_options, opt); } void options_mem_dupe(void *data, struct fio_option *options) { struct fio_option *o; char **ptr; for (o = &options[0]; o->name; o++) { if (o->type != FIO_OPT_STR_STORE) continue; ptr = td_var(data, o->off1); if (*ptr) *ptr = strdup(*ptr); } } /* * dupe FIO_OPT_STR_STORE options */ void fio_options_mem_dupe(struct thread_data *td) { options_mem_dupe(&td->o, fio_options); if (td->eo && td->io_ops) { void *oldeo = td->eo; td->eo = malloc(td->io_ops->option_struct_size); memcpy(td->eo, oldeo, td->io_ops->option_struct_size); options_mem_dupe(td->eo, td->io_ops->options); } } unsigned int fio_get_kb_base(void *data) { struct thread_options *o = data; unsigned int kb_base = 0; if (o) kb_base = o->kb_base; if (!kb_base) kb_base = 1024; return kb_base; } int add_option(struct fio_option *o) { struct fio_option *__o; int opt_index = 0; __o = fio_options; while (__o->name) { opt_index++; __o++; } memcpy(&fio_options[opt_index], o, sizeof(*o)); return 0; } void invalidate_profile_options(const char *prof_name) { struct fio_option *o; o = fio_options; while (o->name) { if (o->prof_name && !strcmp(o->prof_name, prof_name)) { o->type = FIO_OPT_INVALID; o->prof_name = NULL; } o++; } } void add_opt_posval(const char *optname, const char *ival, const char *help) { struct fio_option *o; unsigned int i; o = find_option(fio_options, optname); if (!o) return; for (i = 0; i < PARSE_MAX_VP; i++) { if (o->posval[i].ival) continue; o->posval[i].ival = ival; o->posval[i].help = help; break; } } void del_opt_posval(const char *optname, const char *ival) { struct fio_option *o; unsigned int i; o = find_option(fio_options, optname); if (!o) return; for (i = 0; i < PARSE_MAX_VP; i++) { if (!o->posval[i].ival) continue; if (strcmp(o->posval[i].ival, ival)) continue; o->posval[i].ival = NULL; o->posval[i].help = NULL; } } void fio_options_free(struct thread_data *td) { options_free(fio_options, td); if (td->eo && td->io_ops && td->io_ops->options) { options_free(td->io_ops->options, td->eo); free(td->eo); td->eo = NULL; } } struct fio_option *fio_option_find(const char *name) { return find_option(fio_options, name); } fio-2.1.3/options.h000066400000000000000000000071631222032232000141310ustar00rootroot00000000000000#ifndef FIO_OPTION_H #define FIO_OPTION_H #define FIO_MAX_OPTS 512 #include #include "parse.h" #include "flist.h" #define td_var_offset(var) ((size_t) &((struct thread_options *)0)->var) int add_option(struct fio_option *); void invalidate_profile_options(const char *); extern char *exec_profile; void add_opt_posval(const char *, const char *, const char *); void del_opt_posval(const char *, const char *); struct thread_data; void fio_options_free(struct thread_data *); extern struct fio_option fio_options[FIO_MAX_OPTS]; static inline int o_match(struct fio_option *o, const char *opt) { if (!strcmp(o->name, opt)) return 1; else if (o->alias && !strcmp(o->alias, opt)) return 1; return 0; } static inline struct fio_option *find_option(struct fio_option *options, const char *opt) { struct fio_option *o; for (o = &options[0]; o->name; o++) if (o_match(o, opt)) return o; return NULL; } struct opt_group { const char *name; unsigned int mask; }; enum opt_category { __FIO_OPT_C_GENERAL = 0, __FIO_OPT_C_IO, __FIO_OPT_C_FILE, __FIO_OPT_C_STAT, __FIO_OPT_C_LOG, __FIO_OPT_C_PROFILE, __FIO_OPT_C_ENGINE, __FIO_OPT_C_NR, FIO_OPT_C_GENERAL = (1U << __FIO_OPT_C_GENERAL), FIO_OPT_C_IO = (1U << __FIO_OPT_C_IO), FIO_OPT_C_FILE = (1U << __FIO_OPT_C_FILE), FIO_OPT_C_STAT = (1U << __FIO_OPT_C_STAT), FIO_OPT_C_LOG = (1U << __FIO_OPT_C_LOG), FIO_OPT_C_PROFILE = (1U << __FIO_OPT_C_PROFILE), FIO_OPT_C_ENGINE = (1U << __FIO_OPT_C_ENGINE), FIO_OPT_C_INVALID = (1U << __FIO_OPT_C_NR), }; enum opt_category_group { __FIO_OPT_G_RATE = 0, __FIO_OPT_G_ZONE, __FIO_OPT_G_RWMIX, __FIO_OPT_G_VERIFY, __FIO_OPT_G_TRIM, __FIO_OPT_G_IOLOG, __FIO_OPT_G_IO_DEPTH, __FIO_OPT_G_IO_FLOW, __FIO_OPT_G_DESC, __FIO_OPT_G_FILENAME, __FIO_OPT_G_IO_BASIC, __FIO_OPT_G_CGROUP, __FIO_OPT_G_RUNTIME, __FIO_OPT_G_PROCESS, __FIO_OPT_G_CRED, __FIO_OPT_G_CLOCK, __FIO_OPT_G_IO_TYPE, __FIO_OPT_G_THINKTIME, __FIO_OPT_G_RANDOM, __FIO_OPT_G_IO_BUF, __FIO_OPT_G_TIOBENCH, __FIO_OPT_G_ERR, __FIO_OPT_G_E4DEFRAG, __FIO_OPT_G_NETIO, __FIO_OPT_G_LIBAIO, __FIO_OPT_G_ACT, __FIO_OPT_G_NR, FIO_OPT_G_RATE = (1U << __FIO_OPT_G_RATE), FIO_OPT_G_ZONE = (1U << __FIO_OPT_G_ZONE), FIO_OPT_G_RWMIX = (1U << __FIO_OPT_G_RWMIX), FIO_OPT_G_VERIFY = (1U << __FIO_OPT_G_VERIFY), FIO_OPT_G_TRIM = (1U << __FIO_OPT_G_TRIM), FIO_OPT_G_IOLOG = (1U << __FIO_OPT_G_IOLOG), FIO_OPT_G_IO_DEPTH = (1U << __FIO_OPT_G_IO_DEPTH), FIO_OPT_G_IO_FLOW = (1U << __FIO_OPT_G_IO_FLOW), FIO_OPT_G_DESC = (1U << __FIO_OPT_G_DESC), FIO_OPT_G_FILENAME = (1U << __FIO_OPT_G_FILENAME), FIO_OPT_G_IO_BASIC = (1U << __FIO_OPT_G_IO_BASIC), FIO_OPT_G_CGROUP = (1U << __FIO_OPT_G_CGROUP), FIO_OPT_G_RUNTIME = (1U << __FIO_OPT_G_RUNTIME), FIO_OPT_G_PROCESS = (1U << __FIO_OPT_G_PROCESS), FIO_OPT_G_CRED = (1U << __FIO_OPT_G_CRED), FIO_OPT_G_CLOCK = (1U << __FIO_OPT_G_CLOCK), FIO_OPT_G_IO_TYPE = (1U << __FIO_OPT_G_IO_TYPE), FIO_OPT_G_THINKTIME = (1U << __FIO_OPT_G_THINKTIME), FIO_OPT_G_RANDOM = (1U << __FIO_OPT_G_RANDOM), FIO_OPT_G_IO_BUF = (1U << __FIO_OPT_G_IO_BUF), FIO_OPT_G_TIOBENCH = (1U << __FIO_OPT_G_TIOBENCH), FIO_OPT_G_ERR = (1U << __FIO_OPT_G_ERR), FIO_OPT_G_E4DEFRAG = (1U << __FIO_OPT_G_E4DEFRAG), FIO_OPT_G_NETIO = (1U << __FIO_OPT_G_NETIO), FIO_OPT_G_LIBAIO = (1U << __FIO_OPT_G_LIBAIO), FIO_OPT_G_ACT = (1U << __FIO_OPT_G_ACT), FIO_OPT_G_INVALID = (1U << __FIO_OPT_G_NR), }; extern struct opt_group *opt_group_from_mask(unsigned int *mask); extern struct opt_group *opt_group_cat_from_mask(unsigned int *mask); extern struct fio_option *fio_option_find(const char *name); extern unsigned int fio_get_kb_base(void *); #endif fio-2.1.3/os/000077500000000000000000000000001222032232000126775ustar00rootroot00000000000000fio-2.1.3/os/binject.h000066400000000000000000000031161222032232000144670ustar00rootroot00000000000000#ifndef BINJECT_H #define BINJECT_H #include #define BINJECT_MAGIC 0x89 #define BINJECT_VER 0x01 #define BINJECT_MAGIC_SHIFT 8 #define BINJECT_VER_MASK ((1 << BINJECT_MAGIC_SHIFT) - 1) struct b_user_cmd { __u16 magic; /* INPUT */ __u16 type; /* INPUT */ __u32 error; /* OUTPUT */ __u32 flags; /* INPUT */ __u32 len; /* INPUT */ __u64 offset; /* INPUT */ __u64 buf; /* INPUT */ __u64 usr_ptr; /* PASSED THROUGH */ __u64 nsec; /* OUTPUT */ }; struct b_ioctl_cmd { int fd; int minor; }; #define BINJECT_IOCTL_CHR 'J' #define B_IOCTL_ADD _IOWR(BINJECT_IOCTL_CHR, 1, struct b_ioctl_cmd) #define B_IOCTL_DEL _IOWR(BINJECT_IOCTL_CHR, 2, struct b_ioctl_cmd) enum { B_TYPE_READ = 0, B_TYPE_WRITE, B_TYPE_DISCARD, B_TYPE_READVOID, B_TYPE_WRITEZERO, B_TYPE_READBARRIER, B_TYPE_WRITEBARRIER, B_TYPE_NR }; enum { __B_FLAG_SYNC = 0, __B_FLAG_UNPLUG, __B_FLAG_NOIDLE, __B_FLAG_BARRIER, __B_FLAG_META, __B_FLAG_RAHEAD, __B_FLAG_FAILFAST_DEV, __B_FLAG_FAILFAST_TRANSPORT, __B_FLAG_FAILFAST_DRIVER, __B_FLAG_NR, B_FLAG_SYNC = 1 << __B_FLAG_SYNC, B_FLAG_UNPLUG = 1 << __B_FLAG_UNPLUG, B_FLAG_NOIDLE = 1 << __B_FLAG_NOIDLE, B_FLAG_BARRIER = 1 << __B_FLAG_BARRIER, B_FLAG_META = 1 << __B_FLAG_META, B_FLAG_RAHEAD = 1 << __B_FLAG_RAHEAD, B_FLAG_FAILFAST_DEV = 1 << __B_FLAG_FAILFAST_DEV, B_FLAG_FAILFAST_TRANSPORT = 1 << __B_FLAG_FAILFAST_TRANSPORT, B_FLAG_FAILFAST_DRIVER = 1 << __B_FLAG_FAILFAST_DRIVER, }; static inline void binject_buc_set_magic(struct b_user_cmd *buc) { buc->magic = (BINJECT_MAGIC << BINJECT_MAGIC_SHIFT) | BINJECT_VER; } #endif fio-2.1.3/os/kcompat.h000066400000000000000000000001601222032232000145030ustar00rootroot00000000000000#ifndef _KCOMPAT_H_ #define _KCOMPAT_H_ #include #define u64 uint64_t #define u32 uint32_t #endif fio-2.1.3/os/os-aix.h000066400000000000000000000016211222032232000142500ustar00rootroot00000000000000#ifndef FIO_OS_AIX_H #define FIO_OS_AIX_H #define FIO_OS os_aix #include #include #include #include #include "../file.h" #define FIO_HAVE_ODIRECT #define FIO_USE_GENERIC_RAND #define FIO_USE_GENERIC_INIT_RANDOM_STATE #define FIO_HAVE_PSHARED_MUTEX #define OS_MAP_ANON MAP_ANON #define OS_MSG_DONTWAIT 0 #define FIO_USE_GENERIC_SWAP static inline int blockdev_invalidate_cache(struct fio_file *f) { return EINVAL; } static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes) { struct devinfo info; if (!ioctl(f->fd, IOCINFO, &info)) { *bytes = (unsigned long long)info.un.scdk.numblks * info.un.scdk.blksize; return 0; } return errno; } static inline unsigned long long os_phys_mem(void) { long mem = sysconf(_SC_AIX_REALMEM); if (mem == -1) return 0; return (unsigned long long) mem * 1024; } #endif fio-2.1.3/os/os-android.h000066400000000000000000000121411222032232000151060ustar00rootroot00000000000000#ifndef FIO_OS_ANDROID_H #define FIO_OS_ANDROID_H #define FIO_OS os_android #include #include #include #include #include #include #include #include #include #include #include #include "binject.h" #include "../file.h" #define FIO_HAVE_DISK_UTIL #define FIO_HAVE_IOSCHED_SWITCH #define FIO_HAVE_IOPRIO #define FIO_HAVE_ODIRECT #define FIO_HAVE_HUGETLB #define FIO_HAVE_BLKTRACE #define FIO_HAVE_PSHARED_MUTEX #define FIO_HAVE_CL_SIZE #define FIO_HAVE_FS_STAT #define FIO_HAVE_TRIM #define FIO_HAVE_GETTID #define FIO_USE_GENERIC_INIT_RANDOM_STATE #define FIO_HAVE_E4_ENG #define FIO_HAVE_BYTEORDER_FUNCS #define FIO_HAVE_MMAP_HUGE #define FIO_NO_HAVE_SHM_H #define OS_MAP_ANON MAP_ANONYMOUS #define posix_madvise madvise #define POSIX_MADV_DONTNEED MADV_DONTNEED #define POSIX_MADV_SEQUENTIAL MADV_SEQUENTIAL #define POSIX_MADV_RANDOM MADV_RANDOM #ifdef MADV_REMOVE #define FIO_MADV_FREE MADV_REMOVE #endif #ifndef MAP_HUGETLB #define MAP_HUGETLB 0x40000 /* arch specific */ #endif /* * The Android NDK doesn't currently export , so define the * necessary stuff here. */ #include #define SHM_HUGETLB 04000 #include #include #include #define ASHMEM_DEVICE "/dev/ashmem" static inline int shmctl (int __shmid, int __cmd, struct shmid_ds *__buf) { int ret=0; if (__cmd == IPC_RMID) { int length = ioctl(__shmid, ASHMEM_GET_SIZE, NULL); struct ashmem_pin pin = {0 , length}; ret = ioctl(__shmid, ASHMEM_UNPIN, &pin); close(__shmid); } return ret; } static inline int shmget (key_t __key, size_t __size, int __shmflg) { int fd,ret; char key[11]; fd = open(ASHMEM_DEVICE, O_RDWR); if (fd < 0) return fd; sprintf(key,"%d",__key); ret = ioctl(fd, ASHMEM_SET_NAME, key); if (ret < 0) goto error; ret = ioctl(fd, ASHMEM_SET_SIZE, __size); if (ret < 0) goto error; return fd; error: close(fd); return ret; } static inline void *shmat (int __shmid, const void *__shmaddr, int __shmflg) { size_t *ptr, size = ioctl(__shmid, ASHMEM_GET_SIZE, NULL); ptr = mmap(NULL, size + sizeof(size_t), PROT_READ | PROT_WRITE, MAP_SHARED, __shmid, 0); *ptr = size; //save size at beginning of buffer, for use with munmap return &ptr[1]; } static inline int shmdt (const void *__shmaddr) { size_t *ptr, size; ptr = (size_t *)__shmaddr; ptr--; size = *ptr; //find mmap size which we stored at the beginning of the buffer return munmap((void *)ptr, size + sizeof(size_t)); } #define SPLICE_DEF_SIZE (64*1024) enum { IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE, }; enum { IOPRIO_WHO_PROCESS = 1, IOPRIO_WHO_PGRP, IOPRIO_WHO_USER, }; #define IOPRIO_BITS 16 #define IOPRIO_CLASS_SHIFT 13 static inline int ioprio_set(int which, int who, int ioprio_class, int ioprio) { /* * If no class is set, assume BE */ if (!ioprio_class) ioprio_class = IOPRIO_CLASS_BE; ioprio |= ioprio_class << IOPRIO_CLASS_SHIFT; return syscall(__NR_ioprio_set, which, who, ioprio); } #ifndef BLKGETSIZE64 #define BLKGETSIZE64 _IOR(0x12,114,size_t) #endif #ifndef BLKFLSBUF #define BLKFLSBUF _IO(0x12,97) #endif #ifndef BLKDISCARD #define BLKDISCARD _IO(0x12,119) #endif static inline int blockdev_invalidate_cache(struct fio_file *f) { return ioctl(f->fd, BLKFLSBUF); } static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes) { if (!ioctl(f->fd, BLKGETSIZE64, bytes)) return 0; return errno; } static inline unsigned long long os_phys_mem(void) { long pagesize, pages; pagesize = sysconf(_SC_PAGESIZE); pages = sysconf(_SC_PHYS_PAGES); if (pages == -1 || pagesize == -1) return 0; return (unsigned long long) pages * (unsigned long long) pagesize; } typedef struct { unsigned short r[3]; } os_random_state_t; static inline void os_random_seed(unsigned long seed, os_random_state_t *rs) { rs->r[0] = seed & 0xffff; seed >>= 16; rs->r[1] = seed & 0xffff; seed >>= 16; rs->r[2] = seed & 0xffff; seed48(rs->r); } static inline long os_random_long(os_random_state_t *rs) { return nrand48(rs->r); } #ifdef O_NOATIME #define FIO_O_NOATIME O_NOATIME #else #define FIO_O_NOATIME 0 #endif #define fio_swap16(x) __bswap_16(x) #define fio_swap32(x) __bswap_32(x) #define fio_swap64(x) __bswap_64(x) #define CACHE_LINE_FILE \ "/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size" static inline int arch_cache_line_size(void) { char size[32]; int fd, ret; fd = open(CACHE_LINE_FILE, O_RDONLY); if (fd < 0) return -1; ret = read(fd, size, sizeof(size)); close(fd); if (ret <= 0) return -1; else return atoi(size); } static inline unsigned long long get_fs_size(const char *path) { unsigned long long ret; struct statfs s; if (statfs(path, &s) < 0) return -1ULL; ret = s.f_bsize; ret *= (unsigned long long) s.f_bfree; return ret; } static inline int os_trim(int fd, unsigned long long start, unsigned long long len) { uint64_t range[2]; range[0] = start; range[1] = len; if (!ioctl(fd, BLKDISCARD, range)) return 0; return errno; } #endif fio-2.1.3/os/os-freebsd.h000066400000000000000000000040441222032232000151030ustar00rootroot00000000000000#ifndef FIO_OS_FREEBSD_H #define FIO_OS_FREEBSD_H #define FIO_OS os_freebsd #include #include #include #include #include #include #include #include "../file.h" #define FIO_HAVE_ODIRECT #define FIO_USE_GENERIC_RAND #define FIO_USE_GENERIC_INIT_RANDOM_STATE #define FIO_HAVE_CHARDEV_SIZE #define FIO_HAVE_GETTID #define FIO_HAVE_CPU_AFFINITY #define OS_MAP_ANON MAP_ANON #define fio_swap16(x) bswap16(x) #define fio_swap32(x) bswap32(x) #define fio_swap64(x) bswap64(x) typedef off_t off64_t; typedef cpuset_t os_cpu_mask_t; #define fio_cpu_clear(mask, cpu) (void) CPU_CLR((cpu), (mask)) #define fio_cpu_set(mask, cpu) (void) CPU_SET((cpu), (mask)) static inline int fio_cpuset_init(os_cpu_mask_t *mask) { CPU_ZERO(mask); return 0; } static inline int fio_cpuset_exit(os_cpu_mask_t *mask) { return 0; } static inline int fio_setaffinity(int pid, os_cpu_mask_t cpumask) { return cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, pid, sizeof(cpumask), &cpumask); } static inline int fio_getaffinity(int pid, os_cpu_mask_t *cpumask) { return cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, pid, sizeof(cpumask), cpumask); } #define FIO_MAX_CPUS CPU_SETSIZE static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes) { off_t size; if (!ioctl(f->fd, DIOCGMEDIASIZE, &size)) { *bytes = size; return 0; } *bytes = 0; return errno; } static inline int chardev_size(struct fio_file *f, unsigned long long *bytes) { return blockdev_size(f, bytes); } static inline int blockdev_invalidate_cache(struct fio_file *f) { return EINVAL; } static inline unsigned long long os_phys_mem(void) { int mib[2] = { CTL_HW, HW_PHYSMEM }; unsigned long long mem; size_t len = sizeof(mem); sysctl(mib, 2, &mem, &len, NULL, 0); return mem; } static inline int gettid(void) { long lwpid; thr_self(&lwpid); return (int) lwpid; } #ifdef MADV_FREE #define FIO_MADV_FREE MADV_FREE #endif #endif fio-2.1.3/os/os-hpux.h000066400000000000000000000035271222032232000144620ustar00rootroot00000000000000#ifndef FIO_OS_HPUX_H #define FIO_OS_HPUX_H #define FIO_OS os_hpux #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../file.h" #define FIO_HAVE_ODIRECT #define FIO_USE_GENERIC_RAND #define FIO_USE_GENERIC_INIT_RANDOM_STATE #define FIO_HAVE_PSHARED_MUTEX #define FIO_HAVE_CHARDEV_SIZE #define OS_MAP_ANON MAP_ANONYMOUS #define OS_MSG_DONTWAIT 0 #define POSIX_MADV_DONTNEED MADV_DONTNEED #define POSIX_MADV_SEQUENTIAL MADV_SEQUENTIAL #define POSIX_MADV_RANDOM MADV_RANDOM #define posix_madvise(ptr, sz, hint) madvise((ptr), (sz), (hint)) #ifndef MSG_WAITALL #define MSG_WAITALL 0x40 #endif #define FIO_USE_GENERIC_SWAP #define FIO_OS_HAVE_AIOCB_TYPEDEF typedef struct aiocb64 os_aiocb_t; static inline int blockdev_invalidate_cache(struct fio_file *f) { return EINVAL; } static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes) { disk_describe_type_ext_t dext; if (!ioctl(f->fd, DIOC_DESCRIBE_EXT, &dext)) { unsigned long long lba; lba = ((uint64_t) dext.maxsva_high << 32) | dext.maxsva_low; *bytes = lba * dext.lgblksz; return 0; } *bytes = 0; return errno; } static inline int chardev_size(struct fio_file *f, unsigned long long *bytes) { return blockdev_size(f, bytes); } static inline unsigned long long os_phys_mem(void) { unsigned long long ret; struct pst_static pst; union pstun pu; pu.pst_static = &pst; if (pstat(PSTAT_STATIC, pu, sizeof(pst), 0, 0) == -1) return 0; ret = pst.physical_memory; ret *= pst.page_size; return ret; } #define FIO_HAVE_CPU_ONLINE_SYSCONF static inline unsigned int cpus_online(void) { return mpctl(MPC_GETNUMSPUS, 0, NULL); } #endif fio-2.1.3/os/os-linux.h000066400000000000000000000116301222032232000146270ustar00rootroot00000000000000#ifndef FIO_OS_LINUX_H #define FIO_OS_LINUX_H #define FIO_OS os_linux #include #include #include #include #include #include #include #include #include #include #include #include #include "binject.h" #include "../file.h" #define FIO_HAVE_CPU_AFFINITY #define FIO_HAVE_DISK_UTIL #define FIO_HAVE_SGIO #define FIO_HAVE_IOPRIO #define FIO_HAVE_IOSCHED_SWITCH #define FIO_HAVE_ODIRECT #define FIO_HAVE_HUGETLB #define FIO_HAVE_RAWBIND #define FIO_HAVE_BLKTRACE #define FIO_HAVE_PSHARED_MUTEX #define FIO_HAVE_CL_SIZE #define FIO_HAVE_CGROUPS #define FIO_HAVE_FS_STAT #define FIO_HAVE_TRIM #define FIO_HAVE_BINJECT #define FIO_HAVE_GETTID #define FIO_USE_GENERIC_INIT_RANDOM_STATE #ifdef MAP_HUGETLB #define FIO_HAVE_MMAP_HUGE #endif #define OS_MAP_ANON MAP_ANONYMOUS typedef cpu_set_t os_cpu_mask_t; typedef struct drand48_data os_random_state_t; #ifdef CONFIG_3ARG_AFFINITY #define fio_setaffinity(pid, cpumask) \ sched_setaffinity((pid), sizeof(cpumask), &(cpumask)) #define fio_getaffinity(pid, ptr) \ sched_getaffinity((pid), sizeof(cpu_set_t), (ptr)) #elif defined(CONFIG_2ARG_AFFINITY) #define fio_setaffinity(pid, cpumask) \ sched_setaffinity((pid), &(cpumask)) #define fio_getaffinity(pid, ptr) \ sched_getaffinity((pid), (ptr)) #endif #define fio_cpu_clear(mask, cpu) (void) CPU_CLR((cpu), (mask)) #define fio_cpu_set(mask, cpu) (void) CPU_SET((cpu), (mask)) static inline int fio_cpuset_init(os_cpu_mask_t *mask) { CPU_ZERO(mask); return 0; } static inline int fio_cpuset_exit(os_cpu_mask_t *mask) { return 0; } #define FIO_MAX_CPUS CPU_SETSIZE enum { IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE, }; enum { IOPRIO_WHO_PROCESS = 1, IOPRIO_WHO_PGRP, IOPRIO_WHO_USER, }; #define IOPRIO_BITS 16 #define IOPRIO_CLASS_SHIFT 13 static inline int ioprio_set(int which, int who, int ioprio_class, int ioprio) { /* * If no class is set, assume BE */ if (!ioprio_class) ioprio_class = IOPRIO_CLASS_BE; ioprio |= ioprio_class << IOPRIO_CLASS_SHIFT; return syscall(__NR_ioprio_set, which, who, ioprio); } static inline int gettid(void) { return syscall(__NR_gettid); } #define SPLICE_DEF_SIZE (64*1024) #ifndef BLKGETSIZE64 #define BLKGETSIZE64 _IOR(0x12,114,size_t) #endif #ifndef BLKFLSBUF #define BLKFLSBUF _IO(0x12,97) #endif #ifndef BLKDISCARD #define BLKDISCARD _IO(0x12,119) #endif static inline int blockdev_invalidate_cache(struct fio_file *f) { return ioctl(f->fd, BLKFLSBUF); } static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes) { if (!ioctl(f->fd, BLKGETSIZE64, bytes)) return 0; return errno; } static inline unsigned long long os_phys_mem(void) { long pagesize, pages; pagesize = sysconf(_SC_PAGESIZE); pages = sysconf(_SC_PHYS_PAGES); if (pages == -1 || pagesize == -1) return 0; return (unsigned long long) pages * (unsigned long long) pagesize; } static inline void os_random_seed(unsigned long seed, os_random_state_t *rs) { srand48_r(seed, rs); } static inline long os_random_long(os_random_state_t *rs) { long val; lrand48_r(rs, &val); return val; } static inline int fio_lookup_raw(dev_t dev, int *majdev, int *mindev) { struct raw_config_request rq; int fd; if (major(dev) != RAW_MAJOR) return 1; /* * we should be able to find /dev/rawctl or /dev/raw/rawctl */ fd = open("/dev/rawctl", O_RDONLY); if (fd < 0) { fd = open("/dev/raw/rawctl", O_RDONLY); if (fd < 0) return 1; } rq.raw_minor = minor(dev); if (ioctl(fd, RAW_GETBIND, &rq) < 0) { close(fd); return 1; } close(fd); *majdev = rq.block_major; *mindev = rq.block_minor; return 0; } #ifdef O_NOATIME #define FIO_O_NOATIME O_NOATIME #else #define FIO_O_NOATIME 0 #endif #ifdef MADV_REMOVE #define FIO_MADV_FREE MADV_REMOVE #endif #define fio_swap16(x) __bswap_16(x) #define fio_swap32(x) __bswap_32(x) #define fio_swap64(x) __bswap_64(x) #define CACHE_LINE_FILE \ "/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size" static inline int arch_cache_line_size(void) { char size[32]; int fd, ret; fd = open(CACHE_LINE_FILE, O_RDONLY); if (fd < 0) return -1; ret = read(fd, size, sizeof(size)); close(fd); if (ret <= 0) return -1; else return atoi(size); } static inline unsigned long long get_fs_size(const char *path) { unsigned long long ret; struct statfs s; if (statfs(path, &s) < 0) return -1ULL; ret = s.f_bsize; ret *= (unsigned long long) s.f_bfree; return ret; } static inline int os_trim(int fd, unsigned long long start, unsigned long long len) { uint64_t range[2]; range[0] = start; range[1] = len; if (!ioctl(fd, BLKDISCARD, range)) return 0; return errno; } #ifdef CONFIG_SCHED_IDLE static inline int fio_set_sched_idle(void) { struct sched_param p = { .sched_priority = 0, }; return sched_setscheduler(gettid(), SCHED_IDLE, &p); } #endif #endif fio-2.1.3/os/os-mac.h000066400000000000000000000071261222032232000142350ustar00rootroot00000000000000#ifndef FIO_OS_APPLE_H #define FIO_OS_APPLE_H #define FIO_OS os_mac #include #include #include #include #include #include #include #include #include #include #include "../file.h" #define FIO_USE_GENERIC_RAND #define FIO_USE_GENERIC_INIT_RANDOM_STATE #define FIO_HAVE_GETTID #define FIO_HAVE_CHARDEV_SIZE #define OS_MAP_ANON MAP_ANON #define fio_swap16(x) OSSwapInt16(x) #define fio_swap32(x) OSSwapInt32(x) #define fio_swap64(x) OSSwapInt64(x) /* * OSX has a pitifully small shared memory segment by default, * so default to a lower number of max jobs supported */ #define FIO_MAX_JOBS 128 typedef off_t off64_t; /* OS X as of 10.6 doesn't have the timer_* functions. * Emulate the functionality using setitimer and sigaction here */ #define MAX_TIMERS 64 typedef unsigned int clockid_t; typedef unsigned int timer_t; struct itimerspec { struct timespec it_value; struct timespec it_interval; }; static struct sigevent fio_timers[MAX_TIMERS]; static unsigned int num_timers = 0; static void sig_alrm(int signum) { union sigval sv; for (int i = 0; i < num_timers; i++) { if (fio_timers[i].sigev_notify_function == NULL) continue; if (fio_timers[i].sigev_notify == SIGEV_THREAD) fio_timers[i].sigev_notify_function(sv); else if (fio_timers[i].sigev_notify == SIGEV_SIGNAL) kill(getpid(), fio_timers[i].sigev_signo); } } static inline int timer_settime(timer_t timerid, int flags, const struct itimerspec *value, struct itimerspec *ovalue) { struct sigaction sa; struct itimerval tv; struct itimerval tv_out; int rc; tv.it_interval.tv_sec = value->it_interval.tv_sec; tv.it_interval.tv_usec = value->it_interval.tv_nsec / 1000; tv.it_value.tv_sec = value->it_value.tv_sec; tv.it_value.tv_usec = value->it_value.tv_nsec / 1000; sa.sa_handler = sig_alrm; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; rc = sigaction(SIGALRM, &sa, NULL); if (!rc) rc = setitimer(ITIMER_REAL, &tv, &tv_out); if (!rc && ovalue != NULL) { ovalue->it_interval.tv_sec = tv_out.it_interval.tv_sec; ovalue->it_interval.tv_nsec = tv_out.it_interval.tv_usec * 1000; ovalue->it_value.tv_sec = tv_out.it_value.tv_sec; ovalue->it_value.tv_nsec = tv_out.it_value.tv_usec * 1000; } return rc; } static inline int timer_delete(timer_t timer) { return 0; } #define FIO_OS_DIRECTIO static inline int fio_set_odirect(int fd) { if (fcntl(fd, F_NOCACHE, 1) == -1) return errno; return 0; } static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes) { uint32_t block_size; uint64_t block_count; if (ioctl(f->fd, DKIOCGETBLOCKCOUNT, &block_count) == -1) return errno; if (ioctl(f->fd, DKIOCGETBLOCKSIZE, &block_size) == -1) return errno; *bytes = block_size; *bytes *= block_count; return 0; } static inline int chardev_size(struct fio_file *f, unsigned long long *bytes) { /* * Could be a raw block device, this is better than just assuming * we can't get the size at all. */ if (!blockdev_size(f, bytes)) return 0; *bytes = -1ULL; return 0; } static inline int blockdev_invalidate_cache(struct fio_file *f) { return EINVAL; } static inline unsigned long long os_phys_mem(void) { int mib[2] = { CTL_HW, HW_PHYSMEM }; unsigned long long mem; size_t len = sizeof(mem); sysctl(mib, 2, &mem, &len, NULL, 0); return mem; } static inline int gettid(void) { return mach_thread_self(); } /* * For some reason, there's no header definition for fdatasync(), even * if it exists. */ extern int fdatasync(int fd); #endif fio-2.1.3/os/os-netbsd.h000066400000000000000000000023131222032232000147450ustar00rootroot00000000000000#ifndef FIO_OS_NETBSD_H #define FIO_OS_NETBSD_H #define FIO_OS os_netbsd #include #include #include /* XXX hack to avoid confilcts between rbtree.h and */ #define rb_node _rb_node #include #undef rb_node #undef rb_left #undef rb_right #include "../file.h" #define FIO_HAVE_ODIRECT #define FIO_USE_GENERIC_BDEV_SIZE #define FIO_USE_GENERIC_RAND #define FIO_USE_GENERIC_INIT_RANDOM_STATE #define FIO_HAVE_GETTID #undef FIO_HAVE_CPU_AFFINITY /* XXX notyet */ #define OS_MAP_ANON MAP_ANON #ifndef PTHREAD_STACK_MIN #define PTHREAD_STACK_MIN 4096 #endif #define fio_swap16(x) bswap16(x) #define fio_swap32(x) bswap32(x) #define fio_swap64(x) bswap64(x) typedef off_t off64_t; static inline int blockdev_invalidate_cache(struct fio_file *f) { return EINVAL; } static inline unsigned long long os_phys_mem(void) { int mib[2] = { CTL_HW, HW_PHYSMEM64 }; uint64_t mem; size_t len = sizeof(mem); sysctl(mib, 2, &mem, &len, NULL, 0); return mem; } static inline int gettid(void) { return (int) _lwp_self(); } #ifdef MADV_FREE #define FIO_MADV_FREE MADV_FREE #endif /* XXX NetBSD doesn't have getopt_long_only */ #define getopt_long_only getopt_long #endif fio-2.1.3/os/os-solaris.h000066400000000000000000000051471222032232000151520ustar00rootroot00000000000000#ifndef FIO_OS_SOLARIS_H #define FIO_OS_SOLARIS_H #define FIO_OS os_solaris #include #include #include #include #include #include #include #include #include "../file.h" #define FIO_HAVE_CPU_AFFINITY #define FIO_HAVE_PSHARED_MUTEX #define FIO_HAVE_CHARDEV_SIZE #define FIO_USE_GENERIC_BDEV_SIZE #define FIO_USE_GENERIC_INIT_RANDOM_STATE #define FIO_HAVE_GETTID #define OS_MAP_ANON MAP_ANON #define OS_RAND_MAX 2147483648UL #define fio_swap16(x) BSWAP_16(x) #define fio_swap32(x) BSWAP_32(x) #define fio_swap64(x) BSWAP_64(x) struct solaris_rand_seed { unsigned short r[3]; }; #ifndef POSIX_MADV_SEQUENTIAL #define posix_madvise madvise #define POSIX_MADV_SEQUENTIAL MADV_SEQUENTIAL #define POSIX_MADV_DONTNEED MADV_DONTNEED #define POSIX_MADV_RANDOM MADV_RANDOM #endif #define os_ctime_r(x, y, z) ctime_r((x), (y), (z)) #define FIO_OS_HAS_CTIME_R typedef psetid_t os_cpu_mask_t; typedef struct solaris_rand_seed os_random_state_t; static inline int chardev_size(struct fio_file *f, unsigned long long *bytes) { struct dk_minfo info; *bytes = 0; if (ioctl(f->fd, DKIOCGMEDIAINFO, &info) < 0) return errno; *bytes = info.dki_lbsize * info.dki_capacity; return 0; } static inline int blockdev_invalidate_cache(struct fio_file *f) { return 0; } static inline unsigned long long os_phys_mem(void) { return 0; } static inline void os_random_seed(unsigned long seed, os_random_state_t *rs) { rs->r[0] = seed & 0xffff; seed >>= 16; rs->r[1] = seed & 0xffff; seed >>= 16; rs->r[2] = seed & 0xffff; seed48(rs->r); } static inline long os_random_long(os_random_state_t *rs) { return nrand48(rs->r); } #define FIO_OS_DIRECTIO extern int directio(int, int); static inline int fio_set_odirect(int fd) { if (directio(fd, DIRECTIO_ON) < 0) return errno; return 0; } /* * pset binding hooks for fio */ #define fio_setaffinity(pid, cpumask) \ pset_bind((cpumask), P_PID, (pid), NULL) #define fio_getaffinity(pid, ptr) ({ 0; }) #define fio_cpu_clear(mask, cpu) pset_assign(PS_NONE, (cpu), NULL) #define fio_cpu_set(mask, cpu) pset_assign(*(mask), (cpu), NULL) static inline int fio_cpuset_init(os_cpu_mask_t *mask) { if (pset_create(mask) < 0) return -1; return 0; } static inline int fio_cpuset_exit(os_cpu_mask_t *mask) { if (pset_destroy(*mask) < 0) return -1; return 0; } static inline int gettid(void) { return pthread_self(); } /* * Should be enough, not aware of what (if any) restrictions Solaris has */ #define FIO_MAX_CPUS 16384 #ifdef MADV_FREE #define FIO_MADV_FREE MADV_FREE #endif #endif fio-2.1.3/os/os-windows.h000066400000000000000000000134141222032232000151640ustar00rootroot00000000000000#ifndef FIO_OS_WINDOWS_H #define FIO_OS_WINDOWS_H #define FIO_OS os_windows #include #include #include #include #include #include #include #include #include "../smalloc.h" #include "../file.h" #include "../log.h" #include "windows/posix.h" #define FIO_HAVE_ODIRECT #define FIO_HAVE_CPU_AFFINITY #define FIO_HAVE_CHARDEV_SIZE #define FIO_HAVE_GETTID #define FIO_USE_GENERIC_RAND #define FIO_PREFERRED_ENGINE "windowsaio" #define FIO_PREFERRED_CLOCK_SOURCE CS_CGETTIME #define FIO_OS_PATH_SEPARATOR "\\" #define FIO_MAX_CPUS MAXIMUM_PROCESSORS #define OS_MAP_ANON MAP_ANON #define fio_swap16(x) _byteswap_ushort(x) #define fio_swap32(x) _byteswap_ulong(x) #define fio_swap64(x) _byteswap_uint64(x) typedef DWORD_PTR os_cpu_mask_t; #define CLOCK_REALTIME 1 #define CLOCK_MONOTONIC 2 #define _SC_PAGESIZE 0x1 #define _SC_NPROCESSORS_ONLN 0x2 #define _SC_PHYS_PAGES 0x4 #define SA_RESTART 0 #define SIGPIPE 0 /* * Windows doesn't have O_DIRECT or O_SYNC, so define them * here so we can reject them at runtime when using the _open * interface (windowsaio uses CreateFile) */ #define O_DIRECT 0x1000000 #define O_SYNC 0x2000000 /* Windows doesn't support madvise, so any values will work */ #define POSIX_MADV_DONTNEED 0 #define POSIX_MADV_SEQUENTIAL 0 #define POSIX_MADV_RANDOM 0 #define F_SETFL 0x1 #define F_GETFL 0x2 #define O_NONBLOCK FIONBIO /* Winsock doesn't support MSG_WAIT */ #define OS_MSG_DONTWAIT 0 #define POLLOUT 1 #define POLLIN 2 #define POLLERR 0 #define POLLHUP 1 #define SIGCONT 0 #define SIGUSR1 1 #define SIGUSR2 2 typedef int sigset_t; typedef int siginfo_t; struct sigaction { void (*sa_handler)(int); sigset_t sa_mask; int sa_flags; void* (*sa_sigaction)(int, siginfo_t *, void*); }; long sysconf(int name); int kill(pid_t pid, int sig); pid_t setsid(void); int setgid(gid_t gid); int setuid(uid_t uid); int nice(int incr); int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); int fsync(int fildes); int fork(void); int fcntl(int fildes, int cmd, ...); int fdatasync(int fildes); int lstat(const char * path, struct stat * buf); uid_t geteuid(void); int nanosleep(const struct timespec *rqtp, struct timespec *rmtp); ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset); ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset); extern void td_fill_rand_seeds(struct thread_data *); static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes) { int rc = 0; HANDLE hFile; GET_LENGTH_INFORMATION info; DWORD outBytes; LARGE_INTEGER size; if (f->hFile == NULL) { hFile = CreateFile(f->file_name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); } else { hFile = f->hFile; } size.QuadPart = 0; if (DeviceIoControl(hFile, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &info, sizeof(info), &outBytes, NULL)) *bytes = info.Length.QuadPart; else rc = EIO; /* If we were passed a POSIX fd, * close the HANDLE we created via CreateFile */ if (hFile != INVALID_HANDLE_VALUE && f->hFile == NULL) CloseHandle(hFile); return rc; } static inline int chardev_size(struct fio_file *f, unsigned long long *bytes) { return blockdev_size(f, bytes); } static inline int blockdev_invalidate_cache(struct fio_file *f) { /* There's no way to invalidate the cache in Windows * so just pretend to succeed */ return 0; } static inline unsigned long long os_phys_mem(void) { long pagesize, pages; pagesize = sysconf(_SC_PAGESIZE); pages = sysconf(_SC_PHYS_PAGES); if (pages == -1 || pagesize == -1) return 0; return (unsigned long long) pages * (unsigned long long) pagesize; } static inline void os_get_tmpdir(char *path, int len) { GetTempPath(len, path); } static inline int gettid(void) { return GetCurrentThreadId(); } static inline int fio_setaffinity(int pid, os_cpu_mask_t cpumask) { HANDLE h; BOOL bSuccess = FALSE; h = OpenThread(THREAD_QUERY_INFORMATION | THREAD_SET_INFORMATION, TRUE, pid); if (h != NULL) { bSuccess = SetThreadAffinityMask(h, cpumask); if (!bSuccess) log_err("fio_setaffinity failed: failed to set thread affinity (pid %d, mask %.16llx)\n", pid, cpumask); CloseHandle(h); } else { log_err("fio_setaffinity failed: failed to get handle for pid %d\n", pid); } return (bSuccess)? 0 : -1; } static inline void fio_getaffinity(int pid, os_cpu_mask_t *mask) { os_cpu_mask_t systemMask; HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, pid); if (h != NULL) { GetProcessAffinityMask(h, mask, &systemMask); CloseHandle(h); } else { log_err("fio_getaffinity failed: failed to get handle for pid %d\n", pid); } } static inline void fio_cpu_clear(os_cpu_mask_t *mask, int cpu) { *mask ^= 1 << (cpu-1); } static inline void fio_cpu_set(os_cpu_mask_t *mask, int cpu) { *mask |= 1 << cpu; } static inline int fio_cpuset_init(os_cpu_mask_t *mask) { *mask = 0; return 0; } static inline int fio_cpuset_exit(os_cpu_mask_t *mask) { return 0; } static inline int init_random_state(struct thread_data *td, unsigned long *rand_seeds, int size) { HCRYPTPROV hCryptProv; if (!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { errno = GetLastError(); log_err("CryptAcquireContext() failed: error %d\n", errno); return 1; } if (!CryptGenRandom(hCryptProv, size, (BYTE*)rand_seeds)) { errno = GetLastError(); log_err("CryptGenRandom() failed, error %d\n", errno); CryptReleaseContext(hCryptProv, 0); return 1; } CryptReleaseContext(hCryptProv, 0); td_fill_rand_seeds(td); return 0; } static inline int fio_set_sched_idle(void) { /* SetThreadPriority returns nonzero for success */ return (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE))? 0 : -1; } #endif /* FIO_OS_WINDOWS_H */ fio-2.1.3/os/os.h000066400000000000000000000137111222032232000134740ustar00rootroot00000000000000#ifndef FIO_OS_H #define FIO_OS_H #include #include #include #include #include #include #include "../arch/arch.h" enum { os_linux = 1, os_aix, os_freebsd, os_hpux, os_mac, os_netbsd, os_solaris, os_windows, os_android, os_nr, }; #if defined(__ANDROID__) #include "os-android.h" #elif defined(__linux__) #include "os-linux.h" #elif defined(__FreeBSD__) #include "os-freebsd.h" #elif defined(__NetBSD__) #include "os-netbsd.h" #elif defined(__sun__) #include "os-solaris.h" #elif defined(__APPLE__) #include "os-mac.h" #elif defined(_AIX) #include "os-aix.h" #elif defined(__hpux) #include "os-hpux.h" #elif defined(WIN32) #include "os-windows.h" #else #error "unsupported os" #endif #ifdef CONFIG_POSIXAIO #include #ifndef FIO_OS_HAVE_AIOCB_TYPEDEF typedef struct aiocb os_aiocb_t; #endif #endif #ifdef FIO_HAVE_SGIO #include #include #endif #ifndef CONFIG_STRSEP #include "../lib/strsep.h" #endif #ifdef MSG_DONTWAIT #define OS_MSG_DONTWAIT MSG_DONTWAIT #endif #ifndef POSIX_FADV_DONTNEED #define POSIX_FADV_DONTNEED (0) #define POSIX_FADV_SEQUENTIAL (0) #define POSIX_FADV_RANDOM (0) #endif #ifndef FIO_HAVE_CPU_AFFINITY #define fio_setaffinity(pid, mask) (0) #define fio_getaffinity(pid, mask) do { } while (0) #define fio_cpu_clear(mask, cpu) do { } while (0) #define fio_cpuset_exit(mask) (-1) typedef unsigned long os_cpu_mask_t; #endif #ifndef FIO_HAVE_IOPRIO #define ioprio_set(which, who, prioclass, prio) (0) #endif #ifndef FIO_HAVE_ODIRECT #define OS_O_DIRECT 0 #else #define OS_O_DIRECT O_DIRECT #endif #ifndef FIO_HAVE_HUGETLB #define SHM_HUGETLB 0 #define MAP_HUGETLB 0 #ifndef FIO_HUGE_PAGE #define FIO_HUGE_PAGE 0 #endif #else #ifndef FIO_HUGE_PAGE #define FIO_HUGE_PAGE 4194304 #endif #endif #ifndef FIO_HAVE_MMAP_HUGE #define MAP_HUGETLB 0 #endif #ifndef FIO_O_NOATIME #define FIO_O_NOATIME 0 #endif #ifndef OS_RAND_MAX #define OS_RAND_MAX RAND_MAX #endif #ifndef FIO_HAVE_RAWBIND #define fio_lookup_raw(dev, majdev, mindev) 1 #endif #ifndef FIO_PREFERRED_ENGINE #define FIO_PREFERRED_ENGINE "sync" #endif #ifndef FIO_OS_PATH_SEPARATOR #define FIO_OS_PATH_SEPARATOR "/" #endif #ifndef FIO_PREFERRED_CLOCK_SOURCE #ifdef CONFIG_CLOCK_GETTIME #define FIO_PREFERRED_CLOCK_SOURCE CS_CGETTIME #else #define FIO_PREFERRED_CLOCK_SOURCE CS_GTOD #endif #endif #ifndef FIO_MAX_JOBS #define FIO_MAX_JOBS 2048 #endif #ifndef CONFIG_SOCKLEN_T typedef unsigned int socklen_t; #endif #ifndef FIO_OS_HAS_CTIME_R #define os_ctime_r(x, y, z) (void) ctime_r((x), (y)) #endif #ifdef FIO_USE_GENERIC_SWAP static inline uint16_t fio_swap16(uint16_t val) { return (val << 8) | (val >> 8); } static inline uint32_t fio_swap32(uint32_t val) { val = ((val & 0xff00ff00UL) >> 8) | ((val & 0x00ff00ffUL) << 8); return (val >> 16) | (val << 16); } static inline uint64_t fio_swap64(uint64_t val) { val = ((val & 0xff00ff00ff00ff00ULL) >> 8) | ((val & 0x00ff00ff00ff00ffULL) << 8); val = ((val & 0xffff0000ffff0000ULL) >> 16) | ((val & 0x0000ffff0000ffffULL) << 16); return (val >> 32) | (val << 32); } #endif #ifndef FIO_HAVE_BYTEORDER_FUNCS #ifdef CONFIG_LITTLE_ENDIAN #define __le16_to_cpu(x) (x) #define __le32_to_cpu(x) (x) #define __le64_to_cpu(x) (x) #define __cpu_to_le16(x) (x) #define __cpu_to_le32(x) (x) #define __cpu_to_le64(x) (x) #else #define __le16_to_cpu(x) fio_swap16(x) #define __le32_to_cpu(x) fio_swap32(x) #define __le64_to_cpu(x) fio_swap64(x) #define __cpu_to_le16(x) fio_swap16(x) #define __cpu_to_le32(x) fio_swap32(x) #define __cpu_to_le64(x) fio_swap64(x) #endif #endif /* FIO_HAVE_BYTEORDER_FUNCS */ #define le16_to_cpu(val) ({ \ uint16_t *__val = &(val); \ __le16_to_cpu(*__val); \ }) #define le32_to_cpu(val) ({ \ uint32_t *__val = &(val); \ __le32_to_cpu(*__val); \ }) #define le64_to_cpu(val) ({ \ uint64_t *__val = &(val); \ __le64_to_cpu(*__val); \ }) #define cpu_to_le16(val) ({ \ uint16_t *__val = &(val); \ __cpu_to_le16(*__val); \ }) #define cpu_to_le32(val) ({ \ uint32_t *__val = &(val); \ __cpu_to_le32(*__val); \ }) #define cpu_to_le64(val) ({ \ uint64_t *__val = &(val); \ __cpu_to_le64(*__val); \ }) #ifndef FIO_HAVE_BLKTRACE static inline int is_blktrace(const char *fname) { return 0; } struct thread_data; static inline int load_blktrace(struct thread_data *td, const char *fname) { return 1; } #endif #define FIO_DEF_CL_SIZE 128 static inline int os_cache_line_size(void) { #ifdef FIO_HAVE_CL_SIZE int ret = arch_cache_line_size(); if (ret <= 0) return FIO_DEF_CL_SIZE; return ret; #else return FIO_DEF_CL_SIZE; #endif } #ifdef FIO_USE_GENERIC_BDEV_SIZE static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes) { off_t end; *bytes = 0; end = lseek(f->fd, 0, SEEK_END); if (end < 0) return errno; *bytes = end; return 0; } #endif #ifdef FIO_USE_GENERIC_RAND typedef unsigned int os_random_state_t; static inline void os_random_seed(unsigned long seed, os_random_state_t *rs) { srand(seed); } static inline long os_random_long(os_random_state_t *rs) { long val; val = rand_r(rs); return val; } #endif #ifdef FIO_USE_GENERIC_INIT_RANDOM_STATE extern void td_fill_rand_seeds(struct thread_data *td); /* * Initialize the various random states we need (random io, block size ranges, * read/write mix, etc). */ static inline int init_random_state(struct thread_data *td, unsigned long *rand_seeds, int size) { int fd; fd = open("/dev/urandom", O_RDONLY); if (fd == -1) { return 1; } if (read(fd, rand_seeds, size) < size) { close(fd); return 1; } close(fd); td_fill_rand_seeds(td); return 0; } #endif #ifndef FIO_HAVE_FS_STAT static inline unsigned long long get_fs_size(const char *path) { return 0; } #endif #ifndef FIO_HAVE_CPU_ONLINE_SYSCONF static inline unsigned int cpus_online(void) { return sysconf(_SC_NPROCESSORS_ONLN); } #endif #ifndef FIO_HAVE_GETTID static inline int gettid(void) { return getpid(); } #endif #endif fio-2.1.3/os/windows/000077500000000000000000000000001222032232000143715ustar00rootroot00000000000000fio-2.1.3/os/windows/dobuild.cmd000066400000000000000000000012651222032232000165040ustar00rootroot00000000000000@echo off setlocal enabledelayedexpansion set /a counter=1 for /f "tokens=3" %%i in (..\..\FIO-VERSION-FILE) do ( if "!counter!"=="1" set FIO_VERSION=%%i set /a counter+=1 ) if "%1"=="x86" set FIO_ARCH=x86 if "%1"=="x64" set FIO_ARCH=x64 if not defined FIO_ARCH ( echo Error: must specify the architecture. echo Usage: dobuild x86 echo Usage: dobuild x64 goto end ) "%WIX%bin\candle" -nologo -arch %FIO_ARCH% install.wxs @if ERRORLEVEL 1 goto end "%WIX%bin\candle" -nologo -arch %FIO_ARCH% examples.wxs @if ERRORLEVEL 1 goto end "%WIX%bin\light" -nologo -sice:ICE61 install.wixobj examples.wixobj -ext WixUIExtension -out %FIO_VERSION%-%FIO_ARCH%.msi :end fio-2.1.3/os/windows/eula.rtf000077500000000000000000000020441222032232000160370ustar00rootroot00000000000000{\rtf1\ansi\ansicpg1252\deff0{\fonttbl{\f0\fswiss\fcharset0 Helvetica;}} {\colortbl ;\red0\green0\blue255;} {\*\generator Msftedit 5.41.21.2510;}\viewkind4\uc1\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\lang9\f0\fs20 FIO - Flexible IO Tester\par Copyright (C) 2010-2013\par \par This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version.\par \par This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par \par You should have received a copy of the GNU General Public License along with this program. If not, see {\field{\*\fldinst{HYPERLINK "http://www.gnu.org/licenses/"}}{\fldrslt{\ul\cf1 http://www.gnu.org/licenses/}}}\f0\fs20 .\par } fio-2.1.3/os/windows/examples.wxs000077500000000000000000000057621222032232000167670ustar00rootroot00000000000000 fio-2.1.3/os/windows/install.wxs000077500000000000000000000051611222032232000166100ustar00rootroot00000000000000 fio@vger.kernel.org http://www.spinics.net/lists/fio/ http://bluestop.org/fio/ fio-2.1.3/os/windows/posix.c000077500000000000000000000527111222032232000157100ustar00rootroot00000000000000/* This file contains functions which implement those POSIX and Linux functions * that MinGW and Microsoft don't provide. The implementations contain just enough * functionality to support fio. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../os-windows.h" #include "../../lib/hweight.h" extern unsigned long mtime_since_now(struct timeval *); extern void fio_gettime(struct timeval *, void *); /* These aren't defined in the MinGW headers */ HRESULT WINAPI StringCchCopyA( char *pszDest, size_t cchDest, const char *pszSrc); HRESULT WINAPI StringCchPrintfA( char *pszDest, size_t cchDest, const char *pszFormat, ...); int vsprintf_s( char *buffer, size_t numberOfElements, const char *format, va_list argptr); int win_to_posix_error(DWORD winerr) { switch (winerr) { case ERROR_FILE_NOT_FOUND: return ENOENT; case ERROR_PATH_NOT_FOUND: return ENOENT; case ERROR_ACCESS_DENIED: return EACCES; case ERROR_INVALID_HANDLE: return EBADF; case ERROR_NOT_ENOUGH_MEMORY: return ENOMEM; case ERROR_INVALID_DATA: return EINVAL; case ERROR_OUTOFMEMORY: return ENOMEM; case ERROR_INVALID_DRIVE: return ENODEV; case ERROR_NOT_SAME_DEVICE: return EXDEV; case ERROR_WRITE_PROTECT: return EROFS; case ERROR_BAD_UNIT: return ENODEV; case ERROR_SHARING_VIOLATION: return EACCES; case ERROR_LOCK_VIOLATION: return EACCES; case ERROR_SHARING_BUFFER_EXCEEDED: return ENOLCK; case ERROR_HANDLE_DISK_FULL: return ENOSPC; case ERROR_NOT_SUPPORTED: return ENOSYS; case ERROR_FILE_EXISTS: return EEXIST; case ERROR_CANNOT_MAKE: return EPERM; case ERROR_INVALID_PARAMETER: return EINVAL; case ERROR_NO_PROC_SLOTS: return EAGAIN; case ERROR_BROKEN_PIPE: return EPIPE; case ERROR_OPEN_FAILED: return EIO; case ERROR_NO_MORE_SEARCH_HANDLES: return ENFILE; case ERROR_CALL_NOT_IMPLEMENTED: return ENOSYS; case ERROR_INVALID_NAME: return ENOENT; case ERROR_WAIT_NO_CHILDREN: return ECHILD; case ERROR_CHILD_NOT_COMPLETE: return EBUSY; case ERROR_DIR_NOT_EMPTY: return ENOTEMPTY; case ERROR_SIGNAL_REFUSED: return EIO; case ERROR_BAD_PATHNAME: return ENOENT; case ERROR_SIGNAL_PENDING: return EBUSY; case ERROR_MAX_THRDS_REACHED: return EAGAIN; case ERROR_BUSY: return EBUSY; case ERROR_ALREADY_EXISTS: return EEXIST; case ERROR_NO_SIGNAL_SENT: return EIO; case ERROR_FILENAME_EXCED_RANGE: return EINVAL; case ERROR_META_EXPANSION_TOO_LONG: return EINVAL; case ERROR_INVALID_SIGNAL_NUMBER: return EINVAL; case ERROR_THREAD_1_INACTIVE: return EINVAL; case ERROR_BAD_PIPE: return EINVAL; case ERROR_PIPE_BUSY: return EBUSY; case ERROR_NO_DATA: return EPIPE; case ERROR_MORE_DATA: return EAGAIN; case ERROR_DIRECTORY: return ENOTDIR; case ERROR_PIPE_CONNECTED: return EBUSY; case ERROR_NO_TOKEN: return EINVAL; case ERROR_PROCESS_ABORTED: return EFAULT; case ERROR_BAD_DEVICE: return ENODEV; case ERROR_BAD_USERNAME: return EINVAL; case ERROR_OPEN_FILES: return EAGAIN; case ERROR_ACTIVE_CONNECTIONS: return EAGAIN; case ERROR_DEVICE_IN_USE: return EAGAIN; case ERROR_INVALID_AT_INTERRUPT_TIME: return EINTR; case ERROR_IO_DEVICE: return EIO; case ERROR_NOT_OWNER: return EPERM; case ERROR_END_OF_MEDIA: return ENOSPC; case ERROR_EOM_OVERFLOW: return ENOSPC; case ERROR_BEGINNING_OF_MEDIA: return ESPIPE; case ERROR_SETMARK_DETECTED: return ESPIPE; case ERROR_NO_DATA_DETECTED: return ENOSPC; case ERROR_POSSIBLE_DEADLOCK: return EDEADLOCK; case ERROR_CRC: return EIO; case ERROR_NEGATIVE_SEEK: return EINVAL; case ERROR_DISK_FULL: return ENOSPC; case ERROR_NOACCESS: return EFAULT; case ERROR_FILE_INVALID: return ENXIO; } return winerr; } int GetNumLogicalProcessors(void) { SYSTEM_LOGICAL_PROCESSOR_INFORMATION *processor_info = NULL; DWORD len = 0; DWORD num_processors = 0; DWORD error = 0; DWORD i; while (!GetLogicalProcessorInformation(processor_info, &len)) { error = GetLastError(); if (error == ERROR_INSUFFICIENT_BUFFER) processor_info = malloc(len); else { log_err("Error: GetLogicalProcessorInformation failed: %d\n", error); return -1; } if (processor_info == NULL) { log_err("Error: failed to allocate memory for GetLogicalProcessorInformation"); return -1; } } for (i = 0; i < len / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); i++) { if (processor_info[i].Relationship == RelationProcessorCore) num_processors += hweight64(processor_info[i].ProcessorMask); } free(processor_info); return num_processors; } long sysconf(int name) { long val = -1; long val2 = -1; SYSTEM_INFO sysInfo; MEMORYSTATUSEX status; switch (name) { case _SC_NPROCESSORS_ONLN: val = GetNumLogicalProcessors(); if (val == -1) log_err("sysconf(_SC_NPROCESSORS_ONLN) failed\n"); break; case _SC_PAGESIZE: GetSystemInfo(&sysInfo); val = sysInfo.dwPageSize; break; case _SC_PHYS_PAGES: status.dwLength = sizeof(status); val2 = sysconf(_SC_PAGESIZE); if (GlobalMemoryStatusEx(&status) && val2 != -1) val = status.ullTotalPhys / val2; else log_err("sysconf(_SC_PHYS_PAGES) failed\n"); break; default: log_err("sysconf(%d) is not implemented\n", name); break; } return val; } char *dl_error = NULL; int dlclose(void *handle) { return !FreeLibrary((HMODULE)handle); } void *dlopen(const char *file, int mode) { HMODULE hMod; hMod = LoadLibrary(file); if (hMod == INVALID_HANDLE_VALUE) dl_error = (char*)"LoadLibrary failed"; else dl_error = NULL; return hMod; } void *dlsym(void *handle, const char *name) { FARPROC fnPtr; fnPtr = GetProcAddress((HMODULE)handle, name); if (fnPtr == NULL) dl_error = (char*)"GetProcAddress failed"; else dl_error = NULL; return fnPtr; } char *dlerror(void) { return dl_error; } int gettimeofday(struct timeval *restrict tp, void *restrict tzp) { FILETIME fileTime; uint64_t unix_time, windows_time; const uint64_t MILLISECONDS_BETWEEN_1601_AND_1970 = 11644473600000; /* Ignore the timezone parameter */ (void)tzp; /* * Windows time is stored as the number 100 ns intervals since January 1 1601. * Conversion details from http://www.informit.com/articles/article.aspx?p=102236&seqNum=3 * Its precision is 100 ns but accuracy is only one clock tick, or normally around 15 ms. */ GetSystemTimeAsFileTime(&fileTime); windows_time = ((uint64_t)fileTime.dwHighDateTime << 32) + fileTime.dwLowDateTime; /* Divide by 10,000 to convert to ms and subtract the time between 1601 and 1970 */ unix_time = (((windows_time)/10000) - MILLISECONDS_BETWEEN_1601_AND_1970); /* unix_time is now the number of milliseconds since 1970 (the Unix epoch) */ tp->tv_sec = unix_time / 1000; tp->tv_usec = (unix_time % 1000) * 1000; return 0; } int sigaction(int sig, const struct sigaction *act, struct sigaction *oact) { int rc = 0; void (*prev_handler)(int); prev_handler = signal(sig, act->sa_handler); if (oact != NULL) oact->sa_handler = prev_handler; if (prev_handler == SIG_ERR) rc = -1; return rc; } int lstat(const char * path, struct stat * buf) { return stat(path, buf); } void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) { DWORD vaProt = 0; void* allocAddr = NULL; if (prot & PROT_NONE) vaProt |= PAGE_NOACCESS; if ((prot & PROT_READ) && !(prot & PROT_WRITE)) vaProt |= PAGE_READONLY; if (prot & PROT_WRITE) vaProt |= PAGE_READWRITE; if ((flags & MAP_ANON) | (flags & MAP_ANONYMOUS)) { allocAddr = VirtualAlloc(addr, len, MEM_COMMIT, vaProt); if (allocAddr == NULL) errno = win_to_posix_error(GetLastError()); } return allocAddr; } int munmap(void *addr, size_t len) { if (!VirtualFree(addr, 0, MEM_RELEASE)) { errno = win_to_posix_error(GetLastError()); return -1; } return 0; } int fork(void) { log_err("%s is not implemented\n", __func__); errno = ENOSYS; return -1; } pid_t setsid(void) { log_err("%s is not implemented\n", __func__); errno = ENOSYS; return -1; } static HANDLE log_file = INVALID_HANDLE_VALUE; void openlog(const char *ident, int logopt, int facility) { if (log_file == INVALID_HANDLE_VALUE) log_file = CreateFileA("syslog.txt", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL); } void closelog(void) { CloseHandle(log_file); log_file = INVALID_HANDLE_VALUE; } void syslog(int priority, const char *message, ... /* argument */) { va_list v; int len; char *output; DWORD bytes_written; if (log_file == INVALID_HANDLE_VALUE) { log_file = CreateFileA("syslog.txt", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL); } if (log_file == INVALID_HANDLE_VALUE) { log_err("syslog: failed to open log file\n"); return; } va_start(v, message); len = _vscprintf(message, v); output = malloc(len + sizeof(char)); vsprintf(output, message, v); WriteFile(log_file, output, len, &bytes_written, NULL); va_end(v); free(output); } int kill(pid_t pid, int sig) { errno = ESRCH; return -1; } /* * This is assumed to be used only by the network code, * and so doesn't try and handle any of the other cases */ int fcntl(int fildes, int cmd, ...) { /* * non-blocking mode doesn't work the same as in BSD sockets, * so ignore it. */ #if 0 va_list ap; int val, opt, status; if (cmd == F_GETFL) return 0; else if (cmd != F_SETFL) { errno = EINVAL; return -1; } va_start(ap, 1); opt = va_arg(ap, int); if (opt & O_NONBLOCK) val = 1; else val = 0; status = ioctlsocket((SOCKET)fildes, opt, &val); if (status == SOCKET_ERROR) { errno = EINVAL; val = -1; } va_end(ap); return val; #endif return 0; } /* * Get the value of a local clock source. * This implementation supports 2 clocks: CLOCK_MONOTONIC provides high-accuracy * relative time, while CLOCK_REALTIME provides a low-accuracy wall time. */ int clock_gettime(clockid_t clock_id, struct timespec *tp) { int rc = 0; if (clock_id == CLOCK_MONOTONIC) { static LARGE_INTEGER freq = {{0,0}}; LARGE_INTEGER counts; uint64_t t; QueryPerformanceCounter(&counts); if (freq.QuadPart == 0) QueryPerformanceFrequency(&freq); tp->tv_sec = counts.QuadPart / freq.QuadPart; /* Get the difference between the number of ns stored * in 'tv_sec' and that stored in 'counts' */ t = tp->tv_sec * freq.QuadPart; t = counts.QuadPart - t; /* 't' now contains the number of cycles since the last second. * We want the number of nanoseconds, so multiply out by 1,000,000,000 * and then divide by the frequency. */ t *= 1000000000; tp->tv_nsec = t / freq.QuadPart; } else if (clock_id == CLOCK_REALTIME) { /* clock_gettime(CLOCK_REALTIME,...) is just an alias for gettimeofday with a * higher-precision field. */ struct timeval tv; gettimeofday(&tv, NULL); tp->tv_sec = tv.tv_sec; tp->tv_nsec = tv.tv_usec * 1000; } else { errno = EINVAL; rc = -1; } return rc; } int mlock(const void * addr, size_t len) { SIZE_T min, max; BOOL success; HANDLE process = GetCurrentProcess(); success = GetProcessWorkingSetSize(process, &min, &max); if (!success) { errno = win_to_posix_error(GetLastError()); return -1; } min += len; max += len; success = SetProcessWorkingSetSize(process, min, max); if (!success) { errno = win_to_posix_error(GetLastError()); return -1; } success = VirtualLock((LPVOID)addr, len); if (!success) { errno = win_to_posix_error(GetLastError()); return -1; } return 0; } int munlock(const void * addr, size_t len) { BOOL success = VirtualUnlock((LPVOID)addr, len); if (!success) { errno = win_to_posix_error(GetLastError()); return -1; } return 0; } pid_t waitpid(pid_t pid, int *stat_loc, int options) { log_err("%s is not implemented\n", __func__); errno = ENOSYS; return -1; } int usleep(useconds_t useconds) { Sleep(useconds / 1000); return 0; } char *basename(char *path) { static char name[MAX_PATH]; int i; if (path == NULL || strlen(path) == 0) return (char*)"."; i = strlen(path) - 1; while (path[i] != '\\' && path[i] != '/' && i >= 0) i--; strncpy(name, path + i + 1, MAX_PATH); return name; } int fsync(int fildes) { HANDLE hFile = (HANDLE)_get_osfhandle(fildes); if (!FlushFileBuffers(hFile)) { errno = win_to_posix_error(GetLastError()); return -1; } return 0; } int nFileMappings = 0; HANDLE fileMappings[1024]; int shmget(key_t key, size_t size, int shmflg) { int mapid = -1; uint32_t size_low = size & 0xFFFFFFFF; uint32_t size_high = ((uint64_t)size) >> 32; HANDLE hMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, (PAGE_EXECUTE_READWRITE | SEC_RESERVE), size_high, size_low, NULL); if (hMapping != NULL) { fileMappings[nFileMappings] = hMapping; mapid = nFileMappings; nFileMappings++; } else { errno = ENOSYS; } return mapid; } void *shmat(int shmid, const void *shmaddr, int shmflg) { void* mapAddr; MEMORY_BASIC_INFORMATION memInfo; mapAddr = MapViewOfFile(fileMappings[shmid], FILE_MAP_ALL_ACCESS, 0, 0, 0); if (mapAddr == NULL) { errno = win_to_posix_error(GetLastError()); return (void*)-1; } if (VirtualQuery(mapAddr, &memInfo, sizeof(memInfo)) == 0) { errno = win_to_posix_error(GetLastError()); return (void*)-1; } mapAddr = VirtualAlloc(mapAddr, memInfo.RegionSize, MEM_COMMIT, PAGE_READWRITE); if (mapAddr == NULL) { errno = win_to_posix_error(GetLastError()); return (void*)-1; } return mapAddr; } int shmdt(const void *shmaddr) { if (!UnmapViewOfFile(shmaddr)) { errno = win_to_posix_error(GetLastError()); return -1; } return 0; } int shmctl(int shmid, int cmd, struct shmid_ds *buf) { if (cmd == IPC_RMID) { fileMappings[shmid] = INVALID_HANDLE_VALUE; return 0; } else { log_err("%s is not implemented\n", __func__); } errno = ENOSYS; return -1; } int setuid(uid_t uid) { log_err("%s is not implemented\n", __func__); errno = ENOSYS; return -1; } int setgid(gid_t gid) { log_err("%s is not implemented\n", __func__); errno = ENOSYS; return -1; } int nice(int incr) { if (incr != 0) { errno = EINVAL; return -1; } return 0; } int getrusage(int who, struct rusage *r_usage) { const uint64_t SECONDS_BETWEEN_1601_AND_1970 = 11644473600; FILETIME cTime, eTime, kTime, uTime; time_t time; HANDLE h; memset(r_usage, 0, sizeof(*r_usage)); if (who == RUSAGE_SELF) { h = GetCurrentProcess(); GetProcessTimes(h, &cTime, &eTime, &kTime, &uTime); } else if (who == RUSAGE_THREAD) { h = GetCurrentThread(); GetThreadTimes(h, &cTime, &eTime, &kTime, &uTime); } else { log_err("fio: getrusage %d is not implemented\n", who); return -1; } time = ((uint64_t)uTime.dwHighDateTime << 32) + uTime.dwLowDateTime; /* Divide by 10,000,000 to get the number of seconds and move the epoch from * 1601 to 1970 */ time = (time_t)(((time)/10000000) - SECONDS_BETWEEN_1601_AND_1970); r_usage->ru_utime.tv_sec = time; /* getrusage() doesn't care about anything other than seconds, so set tv_usec to 0 */ r_usage->ru_utime.tv_usec = 0; time = ((uint64_t)kTime.dwHighDateTime << 32) + kTime.dwLowDateTime; /* Divide by 10,000,000 to get the number of seconds and move the epoch from * 1601 to 1970 */ time = (time_t)(((time)/10000000) - SECONDS_BETWEEN_1601_AND_1970); r_usage->ru_stime.tv_sec = time; r_usage->ru_stime.tv_usec = 0; return 0; } int posix_madvise(void *addr, size_t len, int advice) { log_err("%s is not implemented\n", __func__); return ENOSYS; } /* Windows doesn't support advice for memory pages. Just ignore it. */ int msync(void *addr, size_t len, int flags) { errno = ENOSYS; return -1; } int fdatasync(int fildes) { return fsync(fildes); } ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset) { int64_t pos = _telli64(fildes); ssize_t len = _write(fildes, buf, nbyte); _lseeki64(fildes, pos, SEEK_SET); return len; } ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset) { int64_t pos = _telli64(fildes); ssize_t len = read(fildes, buf, nbyte); _lseeki64(fildes, pos, SEEK_SET); return len; } ssize_t readv(int fildes, const struct iovec *iov, int iovcnt) { log_err("%s is not implemented\n", __func__); errno = ENOSYS; return -1; } ssize_t writev(int fildes, const struct iovec *iov, int iovcnt) { log_err("%s is not implemented\n", __func__); errno = ENOSYS; return -1; } long long strtoll(const char *restrict str, char **restrict endptr, int base) { return _strtoi64(str, endptr, base); } int poll(struct pollfd fds[], nfds_t nfds, int timeout) { struct timeval tv; struct timeval *to = NULL; fd_set readfds, writefds, exceptfds; int i; int rc; if (timeout != -1) { to = &tv; to->tv_sec = timeout / 1000; to->tv_usec = (timeout % 1000) * 1000; } FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); for (i = 0; i < nfds; i++) { if (fds[i].fd < 0) { fds[i].revents = 0; continue; } if (fds[i].events & POLLIN) FD_SET(fds[i].fd, &readfds); if (fds[i].events & POLLOUT) FD_SET(fds[i].fd, &writefds); FD_SET(fds[i].fd, &exceptfds); } rc = select(nfds, &readfds, &writefds, &exceptfds, to); if (rc != SOCKET_ERROR) { for (i = 0; i < nfds; i++) { if (fds[i].fd < 0) { continue; } if ((fds[i].events & POLLIN) && FD_ISSET(fds[i].fd, &readfds)) fds[i].revents |= POLLIN; if ((fds[i].events & POLLOUT) && FD_ISSET(fds[i].fd, &writefds)) fds[i].revents |= POLLOUT; if (FD_ISSET(fds[i].fd, &exceptfds)) fds[i].revents |= POLLHUP; } } return rc; } int nanosleep(const struct timespec *rqtp, struct timespec *rmtp) { struct timeval tv; DWORD ms_remaining; DWORD ms_total = (rqtp->tv_sec * 1000) + (rqtp->tv_nsec / 1000000.0); if (ms_total == 0) ms_total = 1; ms_remaining = ms_total; /* Since Sleep() can sleep for less than the requested time, add a loop to ensure we only return after the requested length of time has elapsed */ do { fio_gettime(&tv, NULL); Sleep(ms_remaining); ms_remaining = ms_total - mtime_since_now(&tv); } while (ms_remaining > 0 && ms_remaining < ms_total); /* this implementation will never sleep for less than the requested time */ if (rmtp != NULL) { rmtp->tv_sec = 0; rmtp->tv_nsec = 0; } return 0; } DIR *opendir(const char *dirname) { struct dirent_ctx *dc = NULL; /* See if we can open it. If not, we'll return an error here */ HANDLE file = CreateFileA(dirname, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (file != INVALID_HANDLE_VALUE) { CloseHandle(file); dc = (struct dirent_ctx*)malloc(sizeof(struct dirent_ctx)); StringCchCopyA(dc->dirname, MAX_PATH, dirname); dc->find_handle = INVALID_HANDLE_VALUE; } else { DWORD error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) errno = ENOENT; else if (error == ERROR_PATH_NOT_FOUND) errno = ENOTDIR; else if (error == ERROR_TOO_MANY_OPEN_FILES) errno = ENFILE; else if (error == ERROR_ACCESS_DENIED) errno = EACCES; else errno = error; } return dc; } int closedir(DIR *dirp) { if (dirp != NULL && dirp->find_handle != INVALID_HANDLE_VALUE) FindClose(dirp->find_handle); free(dirp); return 0; } struct dirent *readdir(DIR *dirp) { static struct dirent de; WIN32_FIND_DATA find_data; if (dirp == NULL) return NULL; if (dirp->find_handle == INVALID_HANDLE_VALUE) { char search_pattern[MAX_PATH]; StringCchPrintfA(search_pattern, MAX_PATH, "%s\\*", dirp->dirname); dirp->find_handle = FindFirstFileA(search_pattern, &find_data); if (dirp->find_handle == INVALID_HANDLE_VALUE) return NULL; } else { if (!FindNextFile(dirp->find_handle, &find_data)) return NULL; } StringCchCopyA(de.d_name, MAX_PATH, find_data.cFileName); de.d_ino = 0; return &de; } uid_t geteuid(void) { log_err("%s is not implemented\n", __func__); errno = ENOSYS; return -1; } const char* inet_ntop(int af, const void *restrict src, char *restrict dst, socklen_t size) { INT status = SOCKET_ERROR; WSADATA wsd; char *ret = NULL; if (af != AF_INET && af != AF_INET6) { errno = EAFNOSUPPORT; return NULL; } WSAStartup(MAKEWORD(2,2), &wsd); if (af == AF_INET) { struct sockaddr_in si; DWORD len = size; memset(&si, 0, sizeof(si)); si.sin_family = af; memcpy(&si.sin_addr, src, sizeof(si.sin_addr)); status = WSAAddressToString((struct sockaddr*)&si, sizeof(si), NULL, dst, &len); } else if (af == AF_INET6) { struct sockaddr_in6 si6; DWORD len = size; memset(&si6, 0, sizeof(si6)); si6.sin6_family = af; memcpy(&si6.sin6_addr, src, sizeof(si6.sin6_addr)); status = WSAAddressToString((struct sockaddr*)&si6, sizeof(si6), NULL, dst, &len); } if (status != SOCKET_ERROR) ret = dst; else errno = ENOSPC; WSACleanup(); return ret; } int inet_pton(int af, const char *restrict src, void *restrict dst) { INT status = SOCKET_ERROR; WSADATA wsd; int ret = 1; if (af != AF_INET && af != AF_INET6) { errno = EAFNOSUPPORT; return -1; } WSAStartup(MAKEWORD(2,2), &wsd); if (af == AF_INET) { struct sockaddr_in si; INT len = sizeof(si); memset(&si, 0, sizeof(si)); si.sin_family = af; status = WSAStringToAddressA((char*)src, af, NULL, (struct sockaddr*)&si, &len); if (status != SOCKET_ERROR) memcpy(dst, &si.sin_addr, sizeof(si.sin_addr)); } else if (af == AF_INET6) { struct sockaddr_in6 si6; INT len = sizeof(si6); memset(&si6, 0, sizeof(si6)); si6.sin6_family = af; status = WSAStringToAddressA((char*)src, af, NULL, (struct sockaddr*)&si6, &len); if (status != SOCKET_ERROR) memcpy(dst, &si6.sin6_addr, sizeof(si6.sin6_addr)); } if (status == SOCKET_ERROR) { errno = ENOSPC; ret = 0; } WSACleanup(); return ret; } fio-2.1.3/os/windows/posix.h000066400000000000000000000004261222032232000157060ustar00rootroot00000000000000#ifndef FIO_WINDOWS_POSIX_H #define FIO_WINDOWS_POSIX_H typedef off_t off64_t; typedef int clockid_t; extern int clock_gettime(clockid_t clock_id, struct timespec *tp); extern int inet_aton(const char *, struct in_addr *); extern int win_to_posix_error(DWORD winerr); #endif fio-2.1.3/os/windows/posix/000077500000000000000000000000001222032232000155335ustar00rootroot00000000000000fio-2.1.3/os/windows/posix/include/000077500000000000000000000000001222032232000171565ustar00rootroot00000000000000fio-2.1.3/os/windows/posix/include/arpa/000077500000000000000000000000001222032232000201015ustar00rootroot00000000000000fio-2.1.3/os/windows/posix/include/arpa/inet.h000066400000000000000000000004621222032232000212130ustar00rootroot00000000000000#ifndef ARPA_INET_H #define ARPA_INET_H #include #include typedef int socklen_t; const char *inet_ntop(int af, const void *restrict src, char *restrict dst, socklen_t size); int inet_pton(int af, const char *restrict src, void *restrict dst); #endif /* ARPA_INET_H */ fio-2.1.3/os/windows/posix/include/asm/000077500000000000000000000000001222032232000177365ustar00rootroot00000000000000fio-2.1.3/os/windows/posix/include/asm/types.h000066400000000000000000000002371222032232000212550ustar00rootroot00000000000000#ifndef ASM_TYPES_H #define ASM_TYPES_H typedef unsigned short __u16; typedef unsigned int __u32; typedef unsigned long long __u64; #endif /* ASM_TYPES_H */ fio-2.1.3/os/windows/posix/include/dirent.h000066400000000000000000000006061222032232000206160ustar00rootroot00000000000000#ifndef DIRENT_H #define DIRENT_H #include struct dirent { ino_t d_ino; /* File serial number */ char d_name[MAX_PATH]; /* Name of entry */ }; struct dirent_ctx { HANDLE find_handle; char dirname[MAX_PATH]; }; typedef struct dirent_ctx DIR; DIR *opendir(const char *dirname); struct dirent *readdir(DIR *dirp); int closedir(DIR *dirp); #endif /* DIRENT_H */ fio-2.1.3/os/windows/posix/include/dlfcn.h000066400000000000000000000003451222032232000204170ustar00rootroot00000000000000#ifndef DLFCN_H #define DLFCN_H #define RTLD_LAZY 1 void *dlopen(const char *file, int mode); int dlclose(void *handle); void *dlsym(void *restrict handle, const char *restrict name); char *dlerror(void); #endif /* DLFCN_H */ fio-2.1.3/os/windows/posix/include/libgen.h000066400000000000000000000001261222032232000205660ustar00rootroot00000000000000#ifndef LIBGEN_H #define LIBGEN_H char *basename(char *path); #endif /* LIBGEN_H */ fio-2.1.3/os/windows/posix/include/netdb.h000066400000000000000000000000661222032232000204250ustar00rootroot00000000000000#ifndef NETDB_H #define NETDB_H #endif /* NETDB_H */ fio-2.1.3/os/windows/posix/include/netinet/000077500000000000000000000000001222032232000206245ustar00rootroot00000000000000fio-2.1.3/os/windows/posix/include/netinet/in.h000066400000000000000000000007101222032232000214010ustar00rootroot00000000000000#ifndef NETINET_IN_H #define NETINET_IN_H #include #include struct in6_addr { uint8_t s6_addr[16]; }; struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* Port number */ uint32_t sin6_flowinfo; /* IPv6 traffic class and flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Set of interfaces for a scope */ }; #endif /* NETINET_IN_H */ fio-2.1.3/os/windows/posix/include/netinet/tcp.h000066400000000000000000000000641222032232000215630ustar00rootroot00000000000000#ifndef NETINET_TCP_H #define NETINET_TCP_H #endif fio-2.1.3/os/windows/posix/include/poll.h000066400000000000000000000000631222032232000202740ustar00rootroot00000000000000#ifndef POLL_H #define POLL_H #endif /* POLL_H */ fio-2.1.3/os/windows/posix/include/semaphore.h000066400000000000000000000001021222032232000213030ustar00rootroot00000000000000#ifndef SEMAPHORE_H #define SEMAPHORE_H #endif /* SEMAPHORE_H */ fio-2.1.3/os/windows/posix/include/sys/000077500000000000000000000000001222032232000177745ustar00rootroot00000000000000fio-2.1.3/os/windows/posix/include/sys/ipc.h000066400000000000000000000000741222032232000207210ustar00rootroot00000000000000#ifndef SYS_IPC_H #define SYS_IPC_H #endif /* SYS_IPC_H */ fio-2.1.3/os/windows/posix/include/sys/mman.h000066400000000000000000000015331222032232000210770ustar00rootroot00000000000000#ifndef SYS_MMAN_H #define SYS_MMAN_H #include #define PROT_NONE 0x1 #define PROT_READ 0x2 #define PROT_WRITE 0x4 #define MAP_ANON 0x1 #define MAP_ANONYMOUS MAP_ANON #define MAP_FIXED 0x2 #define MAP_HASSEMAPHORE 0x4 #define MAP_INHERIT 0x8 #define MAP_NOCORE 0x10 #define MAP_NOSYNC 0x20 #define MAP_PREFAULT_READ 0x40 #define MAP_PRIVATE 0x80 #define MAP_SHARED 0x100 #define MAP_STACK 0x200 #define MAP_FAILED NULL #define MS_ASYNC 0x1 #define MS_SYNC 0x2 #define MS_INVALIDATE 0x3 int posix_madvise(void *addr, size_t len, int advice); void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); int munmap(void *addr, size_t len); int msync(void *addr, size_t len, int flags); int munlock(const void * addr, size_t len); int mlock(const void *addr, size_t len); #endif /* SYS_MMAN_H */ fio-2.1.3/os/windows/posix/include/sys/poll.h000066400000000000000000000003121222032232000211070ustar00rootroot00000000000000#ifndef SYS_POLL_H #define SYS_POLL_H typedef int nfds_t; struct pollfd { int fd; short events; short revents; }; int poll(struct pollfd fds[], nfds_t nfds, int timeout); #endif /* SYS_POLL_H */ fio-2.1.3/os/windows/posix/include/sys/resource.h000066400000000000000000000004621222032232000217760ustar00rootroot00000000000000#ifndef SYS_RESOURCE_H #define SYS_RESOURCE_H #define RUSAGE_SELF 0 #define RUSAGE_THREAD 1 struct rusage { struct timeval ru_utime; struct timeval ru_stime; int ru_nvcsw; int ru_minflt; int ru_majflt; int ru_nivcsw; }; int getrusage(int who, struct rusage *r_usage); #endif /* SYS_RESOURCE_H */ fio-2.1.3/os/windows/posix/include/sys/shm.h000066400000000000000000000022101222032232000207270ustar00rootroot00000000000000#ifndef SYS_SHM_H #define SYS_SHM_H #define IPC_RMID 0x1 #define IPC_CREAT 0x2 #define IPC_PRIVATE 0x4 typedef int uid_t; typedef int gid_t; typedef int shmatt_t; typedef int key_t; struct ipc_perm { uid_t uid; /* owner's user ID */ gid_t gid; /* owner's group ID */ uid_t cuid; /* creator's user ID */ gid_t cgid; /* creator's group ID */ mode_t mode; /* read/write permission */ }; struct shmid_ds { struct ipc_perm shm_perm; /* operation permission structure */ size_t shm_segsz; /* size of segment in bytes */ pid_t shm_lpid; /* process ID of last shared memory operation */ pid_t shm_cpid; /* process ID of creator */ shmatt_t shm_nattch; /* number of current attaches */ time_t shm_atime; /* time of last shmat() */ time_t shm_dtime; /* time of last shmdt() */ time_t shm_ctime; /* time of last change by shmctl() */ }; int shmctl(int shmid, int cmd, struct shmid_ds *buf); int shmget(key_t key, size_t size, int shmflg); void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shmaddr); #endif /* SYS_SHM_H */ fio-2.1.3/os/windows/posix/include/sys/socket.h000066400000000000000000000001051222032232000214310ustar00rootroot00000000000000#ifndef SYS_SOCKET_H #define SYS_SOCKET_H #endif /* SYS_SOCKET_H */ fio-2.1.3/os/windows/posix/include/sys/uio.h000066400000000000000000000006241222032232000207430ustar00rootroot00000000000000#ifndef SYS_UIO_H #define SYS_UIO_H #include #include struct iovec { void *iov_base; /* Base address of a memory region for input or output */ size_t iov_len; /* The size of the memory pointed to by iov_base */ }; ssize_t readv(int fildes, const struct iovec *iov, int iovcnt); ssize_t writev(int fildes, const struct iovec *iov, int iovcnt); #endif /* SYS_UIO_H */ fio-2.1.3/os/windows/posix/include/sys/un.h000066400000000000000000000003341222032232000205670ustar00rootroot00000000000000#ifndef SYS_UN_H #define SYS_UN_H typedef int sa_family_t; typedef int in_port_t; struct sockaddr_un { sa_family_t sun_family; /* Address family */ char sun_path[]; /* Socket pathname */ }; #endif /* SYS_UN_H */ fio-2.1.3/os/windows/posix/include/sys/wait.h000066400000000000000000000003471222032232000211150ustar00rootroot00000000000000#ifndef SYS_WAIT_H #define SYS_WAIT_H #define WIFSIGNALED(a) 0 #define WIFEXITED(a) 0 #define WTERMSIG(a) 0 #define WEXITSTATUS(a) 0 #define WNOHANG 0 pid_t waitpid(pid_t, int *stat_loc, int options); #endif /* SYS_WAIT_H */ fio-2.1.3/os/windows/posix/include/syslog.h000066400000000000000000000004631222032232000206520ustar00rootroot00000000000000#ifndef SYSLOG_H #define SYSLOG_H int syslog(); #define LOG_INFO 0x1 #define LOG_ERROR 0x2 #define LOG_WARN 0x4 #define LOG_NDELAY 0x1 #define LOG_NOWAIT 0x2 #define LOG_PID 0x4 #define LOG_USER 0x8 void closelog(void); void openlog(const char *ident, int logopt, int facility); #endif /* SYSLOG_H */ fio-2.1.3/parse.c000066400000000000000000000572731222032232000135520ustar00rootroot00000000000000/* * This file contains the ini and command liner parser main. */ #include #include #include #include #include #include #include #include #include #include #include "parse.h" #include "debug.h" #include "options.h" #include "minmax.h" #include "lib/ieee754.h" static struct fio_option *__fio_options; static int vp_cmp(const void *p1, const void *p2) { const struct value_pair *vp1 = p1; const struct value_pair *vp2 = p2; return strlen(vp2->ival) - strlen(vp1->ival); } static void posval_sort(struct fio_option *o, struct value_pair *vpmap) { const struct value_pair *vp; int entries; memset(vpmap, 0, PARSE_MAX_VP * sizeof(struct value_pair)); for (entries = 0; entries < PARSE_MAX_VP; entries++) { vp = &o->posval[entries]; if (!vp->ival || vp->ival[0] == '\0') break; memcpy(&vpmap[entries], vp, sizeof(*vp)); } qsort(vpmap, entries, sizeof(struct value_pair), vp_cmp); } static void show_option_range(struct fio_option *o, int (*logger)(const char *format, ...)) { if (o->type == FIO_OPT_FLOAT_LIST) { if (o->minfp == DBL_MIN && o->maxfp == DBL_MAX) return; logger("%20s: min=%f", "range", o->minfp); if (o->maxfp != DBL_MAX) logger(", max=%f", o->maxfp); logger("\n"); } else if (!o->posval[0].ival) { if (!o->minval && !o->maxval) return; logger("%20s: min=%d", "range", o->minval); if (o->maxval) logger(", max=%d", o->maxval); logger("\n"); } } static void show_option_values(struct fio_option *o) { int i; for (i = 0; i < PARSE_MAX_VP; i++) { const struct value_pair *vp = &o->posval[i]; if (!vp->ival) continue; log_info("%20s: %-10s", i == 0 ? "valid values" : "", vp->ival); if (vp->help) log_info(" %s", vp->help); log_info("\n"); } if (i) log_info("\n"); } static void show_option_help(struct fio_option *o, int is_err) { const char *typehelp[] = { "invalid", "string (opt=bla)", "string (opt=bla)", "string with possible k/m/g postfix (opt=4k)", "string with time postfix (opt=10s)", "string (opt=bla)", "string with dual range (opt=1k-4k,4k-8k)", "integer value (opt=100)", "boolean value (opt=1)", "list of floating point values separated by ':' (opt=5.9:7.8)", "no argument (opt)", "deprecated", }; int (*logger)(const char *format, ...); if (is_err) logger = log_err; else logger = log_info; if (o->alias) logger("%20s: %s\n", "alias", o->alias); logger("%20s: %s\n", "type", typehelp[o->type]); logger("%20s: %s\n", "default", o->def ? o->def : "no default"); if (o->prof_name) logger("%20s: only for profile '%s'\n", "valid", o->prof_name); show_option_range(o, logger); show_option_values(o); } static unsigned long get_mult_time(char c) { switch (c) { case 'm': case 'M': return 60; case 'h': case 'H': return 60 * 60; case 'd': case 'D': return 24 * 60 * 60; default: return 1; } } static int is_separator(char c) { switch (c) { case ':': case '-': case ',': case '/': return 1; default: return 0; } } static unsigned long long __get_mult_bytes(const char *p, void *data, int *percent) { unsigned int kb_base = fio_get_kb_base(data); unsigned long long ret = 1; unsigned int i, pow = 0, mult = kb_base; char *c; if (!p) return 1; c = strdup(p); for (i = 0; i < strlen(c); i++) { c[i] = tolower(c[i]); if (is_separator(c[i])) { c[i] = '\0'; break; } } if (!strncmp("pib", c, 3)) { pow = 5; mult = 1000; } else if (!strncmp("tib", c, 3)) { pow = 4; mult = 1000; } else if (!strncmp("gib", c, 3)) { pow = 3; mult = 1000; } else if (!strncmp("mib", c, 3)) { pow = 2; mult = 1000; } else if (!strncmp("kib", c, 3)) { pow = 1; mult = 1000; } else if (!strncmp("p", c, 1) || !strncmp("pb", c, 2)) pow = 5; else if (!strncmp("t", c, 1) || !strncmp("tb", c, 2)) pow = 4; else if (!strncmp("g", c, 1) || !strncmp("gb", c, 2)) pow = 3; else if (!strncmp("m", c, 1) || !strncmp("mb", c, 2)) pow = 2; else if (!strncmp("k", c, 1) || !strncmp("kb", c, 2)) pow = 1; else if (!strncmp("%", c, 1)) { *percent = 1; free(c); return ret; } while (pow--) ret *= (unsigned long long) mult; free(c); return ret; } static unsigned long long get_mult_bytes(const char *str, int len, void *data, int *percent) { const char *p = str; int digit_seen = 0; if (len < 2) return __get_mult_bytes(str, data, percent); /* * Go forward until we hit a non-digit, or +/- sign */ while ((p - str) <= len) { if (!isdigit((int) *p) && (((*p != '+') && (*p != '-')) || digit_seen)) break; digit_seen |= isdigit((int) *p); p++; } if (!isalpha((int) *p) && (*p != '%')) p = NULL; return __get_mult_bytes(p, data, percent); } /* * Convert string into a floating number. Return 1 for success and 0 otherwise. */ int str_to_float(const char *str, double *val) { return (1 == sscanf(str, "%lf", val)); } /* * convert string into decimal value, noting any size suffix */ int str_to_decimal(const char *str, long long *val, int kilo, void *data) { int len, base; len = strlen(str); if (!len) return 1; if (strstr(str, "0x") || strstr(str, "0X")) base = 16; else base = 10; *val = strtoll(str, NULL, base); if (*val == LONG_MAX && errno == ERANGE) return 1; if (kilo) { unsigned long long mult; int perc = 0; mult = get_mult_bytes(str, len, data, &perc); if (perc) *val = -1ULL - *val; else *val *= mult; } else *val *= get_mult_time(str[len - 1]); return 0; } int check_str_bytes(const char *p, long long *val, void *data) { return str_to_decimal(p, val, 1, data); } int check_str_time(const char *p, long long *val) { return str_to_decimal(p, val, 0, NULL); } void strip_blank_front(char **p) { char *s = *p; if (!strlen(s)) return; while (isspace((int) *s)) s++; *p = s; } void strip_blank_end(char *p) { char *start = p, *s; if (!strlen(p)) return; s = strchr(p, ';'); if (s) *s = '\0'; s = strchr(p, '#'); if (s) *s = '\0'; if (s) p = s; s = p + strlen(p); while ((isspace((int) *s) || iscntrl((int) *s)) && (s > start)) s--; *(s + 1) = '\0'; } static int check_range_bytes(const char *str, long *val, void *data) { long long __val; if (!str_to_decimal(str, &__val, 1, data)) { *val = __val; return 0; } return 1; } static int check_int(const char *p, int *val) { if (!strlen(p)) return 1; if (strstr(p, "0x") || strstr(p, "0X")) { if (sscanf(p, "%x", val) == 1) return 0; } else { if (sscanf(p, "%u", val) == 1) return 0; } return 1; } static int opt_len(const char *str) { char *postfix; postfix = strchr(str, ':'); if (!postfix) return strlen(str); return (int)(postfix - str); } static int str_match_len(const struct value_pair *vp, const char *str) { return max(strlen(vp->ival), opt_len(str)); } #define val_store(ptr, val, off, or, data) \ do { \ ptr = td_var((data), (off)); \ if ((or)) \ *ptr |= (val); \ else \ *ptr = (val); \ } while (0) static int __handle_option(struct fio_option *o, const char *ptr, void *data, int first, int more, int curr) { int il=0, *ilp; fio_fp64_t *flp; long long ull, *ullp; long ul1, ul2; double uf; char **cp = NULL; int ret = 0, is_time = 0; const struct value_pair *vp; struct value_pair posval[PARSE_MAX_VP]; int i, all_skipped = 1; dprint(FD_PARSE, "__handle_option=%s, type=%d, ptr=%s\n", o->name, o->type, ptr); if (!ptr && o->type != FIO_OPT_STR_SET && o->type != FIO_OPT_STR) { log_err("Option %s requires an argument\n", o->name); return 1; } switch (o->type) { case FIO_OPT_STR: case FIO_OPT_STR_MULTI: { fio_opt_str_fn *fn = o->cb; posval_sort(o, posval); ret = 1; for (i = 0; i < PARSE_MAX_VP; i++) { vp = &posval[i]; if (!vp->ival || vp->ival[0] == '\0') continue; all_skipped = 0; if (!strncmp(vp->ival, ptr, str_match_len(vp, ptr))) { ret = 0; if (o->roff1) { if (vp->or) *(unsigned int *) o->roff1 |= vp->oval; else *(unsigned int *) o->roff1 = vp->oval; } else { if (!o->off1) continue; val_store(ilp, vp->oval, o->off1, vp->or, data); } continue; } } if (ret && !all_skipped) show_option_values(o); else if (fn) ret = fn(data, ptr); break; } case FIO_OPT_STR_VAL_TIME: is_time = 1; case FIO_OPT_INT: case FIO_OPT_STR_VAL: { fio_opt_str_val_fn *fn = o->cb; char tmp[128], *p; strncpy(tmp, ptr, sizeof(tmp) - 1); p = strchr(tmp, ','); if (p) *p = '\0'; if (is_time) ret = check_str_time(tmp, &ull); else ret = check_str_bytes(tmp, &ull, data); dprint(FD_PARSE, " ret=%d, out=%llu\n", ret, ull); if (ret) break; if (o->maxval && ull > o->maxval) { log_err("max value out of range: %llu" " (%u max)\n", ull, o->maxval); return 1; } if (o->minval && ull < o->minval) { log_err("min value out of range: %llu" " (%u min)\n", ull, o->minval); return 1; } if (o->posval[0].ival) { posval_sort(o, posval); ret = 1; for (i = 0; i < PARSE_MAX_VP; i++) { vp = &posval[i]; if (!vp->ival || vp->ival[0] == '\0') continue; if (vp->oval == ull) { ret = 0; break; } } if (ret) { log_err("fio: value %llu not allowed:\n", ull); show_option_values(o); return 1; } } if (fn) ret = fn(data, &ull); else { if (o->type == FIO_OPT_INT) { if (first) { if (o->roff1) *(unsigned int *) o->roff1 = ull; else val_store(ilp, ull, o->off1, 0, data); } if (curr == 1) { if (o->roff2) *(unsigned int *) o->roff2 = ull; else if (o->off2) val_store(ilp, ull, o->off2, 0, data); } if (curr == 2) { if (o->roff3) *(unsigned int *) o->roff3 = ull; else if (o->off3) val_store(ilp, ull, o->off3, 0, data); } if (!more) { if (curr < 1) { if (o->roff2) *(unsigned int *) o->roff2 = ull; else if (o->off2) val_store(ilp, ull, o->off2, 0, data); } if (curr < 2) { if (o->roff3) *(unsigned int *) o->roff3 = ull; else if (o->off3) val_store(ilp, ull, o->off3, 0, data); } } } else { if (first) { if (o->roff1) *(unsigned long long *) o->roff1 = ull; else val_store(ullp, ull, o->off1, 0, data); } if (!more) { if (o->roff2) *(unsigned long long *) o->roff2 = ull; else if (o->off2) val_store(ullp, ull, o->off2, 0, data); } } } break; } case FIO_OPT_FLOAT_LIST: { char *cp2; if (first) { /* ** Initialize precision to 0 and zero out list ** in case specified list is shorter than default */ ul2 = 0; ilp = td_var(data, o->off2); *ilp = ul2; flp = td_var(data, o->off1); for(i = 0; i < o->maxlen; i++) flp[i].u.f = 0.0; } if (curr >= o->maxlen) { log_err("the list exceeding max length %d\n", o->maxlen); return 1; } if (!str_to_float(ptr, &uf)) { log_err("not a floating point value: %s\n", ptr); return 1; } if (uf > o->maxfp) { log_err("value out of range: %f" " (range max: %f)\n", uf, o->maxfp); return 1; } if (uf < o->minfp) { log_err("value out of range: %f" " (range min: %f)\n", uf, o->minfp); return 1; } flp = td_var(data, o->off1); flp[curr].u.f = uf; dprint(FD_PARSE, " out=%f\n", uf); /* ** Calculate precision for output by counting ** number of digits after period. Find first ** period in entire remaining list each time */ cp2 = strchr(ptr, '.'); if (cp2 != NULL) { int len = 0; while (*++cp2 != '\0' && *cp2 >= '0' && *cp2 <= '9') len++; ilp = td_var(data, o->off2); if (len > *ilp) *ilp = len; } break; } case FIO_OPT_STR_STORE: { fio_opt_str_fn *fn = o->cb; if (o->roff1 || o->off1) { if (o->roff1) cp = (char **) o->roff1; else if (o->off1) cp = td_var(data, o->off1); *cp = strdup(ptr); } if (fn) ret = fn(data, ptr); else if (o->posval[0].ival) { posval_sort(o, posval); ret = 1; for (i = 0; i < PARSE_MAX_VP; i++) { vp = &posval[i]; if (!vp->ival || vp->ival[0] == '\0') continue; all_skipped = 0; if (!strncmp(vp->ival, ptr, str_match_len(vp, ptr))) { char *rest; ret = 0; if (vp->cb) fn = vp->cb; rest = strstr(*cp ?: ptr, ":"); if (rest) { if (*cp) *rest = '\0'; ptr = rest + 1; } else ptr = NULL; break; } } } if (!all_skipped) { if (ret && !*cp) show_option_values(o); else if (ret && *cp) ret = 0; else if (fn && ptr) ret = fn(data, ptr); } break; } case FIO_OPT_RANGE: { char tmp[128]; char *p1, *p2; strncpy(tmp, ptr, sizeof(tmp) - 1); /* Handle bsrange with separate read,write values: */ p1 = strchr(tmp, ','); if (p1) *p1 = '\0'; p1 = strchr(tmp, '-'); if (!p1) { p1 = strchr(tmp, ':'); if (!p1) { ret = 1; break; } } p2 = p1 + 1; *p1 = '\0'; p1 = tmp; ret = 1; if (!check_range_bytes(p1, &ul1, data) && !check_range_bytes(p2, &ul2, data)) { ret = 0; if (ul1 > ul2) { unsigned long foo = ul1; ul1 = ul2; ul2 = foo; } if (first) { if (o->roff1) *(unsigned int *) o->roff1 = ul1; else val_store(ilp, ul1, o->off1, 0, data); if (o->roff2) *(unsigned int *) o->roff2 = ul2; else val_store(ilp, ul2, o->off2, 0, data); } if (curr == 1) { if (o->roff3 && o->roff4) { *(unsigned int *) o->roff3 = ul1; *(unsigned int *) o->roff4 = ul2; } else if (o->off3 && o->off4) { val_store(ilp, ul1, o->off3, 0, data); val_store(ilp, ul2, o->off4, 0, data); } } if (curr == 2) { if (o->roff5 && o->roff6) { *(unsigned int *) o->roff5 = ul1; *(unsigned int *) o->roff6 = ul2; } else if (o->off5 && o->off6) { val_store(ilp, ul1, o->off5, 0, data); val_store(ilp, ul2, o->off6, 0, data); } } if (!more) { if (curr < 1) { if (o->roff3 && o->roff4) { *(unsigned int *) o->roff3 = ul1; *(unsigned int *) o->roff4 = ul2; } else if (o->off3 && o->off4) { val_store(ilp, ul1, o->off3, 0, data); val_store(ilp, ul2, o->off4, 0, data); } } if (curr < 2) { if (o->roff5 && o->roff6) { *(unsigned int *) o->roff5 = ul1; *(unsigned int *) o->roff6 = ul2; } else if (o->off5 && o->off6) { val_store(ilp, ul1, o->off5, 0, data); val_store(ilp, ul2, o->off6, 0, data); } } } } break; } case FIO_OPT_BOOL: case FIO_OPT_STR_SET: { fio_opt_int_fn *fn = o->cb; if (ptr) ret = check_int(ptr, &il); else if (o->type == FIO_OPT_BOOL) ret = 1; else il = 1; dprint(FD_PARSE, " ret=%d, out=%d\n", ret, il); if (ret) break; if (o->maxval && il > (int) o->maxval) { log_err("max value out of range: %d (%d max)\n", il, o->maxval); return 1; } if (o->minval && il < o->minval) { log_err("min value out of range: %d (%d min)\n", il, o->minval); return 1; } if (o->neg) il = !il; if (fn) ret = fn(data, &il); else { if (first) { if (o->roff1) *(unsigned int *)o->roff1 = il; else val_store(ilp, il, o->off1, 0, data); } if (!more) { if (o->roff2) *(unsigned int *) o->roff2 = il; else if (o->off2) val_store(ilp, il, o->off2, 0, data); } } break; } case FIO_OPT_DEPRECATED: log_info("Option %s is deprecated\n", o->name); ret = 1; break; default: log_err("Bad option type %u\n", o->type); ret = 1; } if (ret) return ret; if (o->verify) { ret = o->verify(o, data); if (ret) { log_err("Correct format for offending option\n"); log_err("%20s: %s\n", o->name, o->help); show_option_help(o, 1); } } return ret; } static int handle_option(struct fio_option *o, const char *__ptr, void *data) { char *o_ptr, *ptr, *ptr2; int ret, done; dprint(FD_PARSE, "handle_option=%s, ptr=%s\n", o->name, __ptr); o_ptr = ptr = NULL; if (__ptr) o_ptr = ptr = strdup(__ptr); /* * See if we have another set of parameters, hidden after a comma. * Do this before parsing this round, to check if we should * copy set 1 options to set 2. */ done = 0; ret = 1; do { int __ret; ptr2 = NULL; if (ptr && (o->type != FIO_OPT_STR_STORE) && (o->type != FIO_OPT_STR) && (o->type != FIO_OPT_FLOAT_LIST)) { ptr2 = strchr(ptr, ','); if (ptr2 && *(ptr2 + 1) == '\0') *ptr2 = '\0'; if (o->type != FIO_OPT_STR_MULTI && o->type != FIO_OPT_RANGE) { if (!ptr2) ptr2 = strchr(ptr, ':'); if (!ptr2) ptr2 = strchr(ptr, '-'); } } else if (ptr && o->type == FIO_OPT_FLOAT_LIST) { ptr2 = strchr(ptr, ':'); } /* * Don't return early if parsing the first option fails - if * we are doing multiple arguments, we can allow the first one * being empty. */ __ret = __handle_option(o, ptr, data, !done, !!ptr2, done); if (ret) ret = __ret; if (!ptr2) break; ptr = ptr2 + 1; done++; } while (1); if (o_ptr) free(o_ptr); return ret; } static struct fio_option *get_option(char *opt, struct fio_option *options, char **post) { struct fio_option *o; char *ret; ret = strchr(opt, '='); if (ret) { *post = ret; *ret = '\0'; ret = opt; (*post)++; strip_blank_end(ret); o = find_option(options, ret); } else { o = find_option(options, opt); *post = NULL; } return o; } static int opt_cmp(const void *p1, const void *p2) { struct fio_option *o; char *s, *foo; int prio1, prio2; prio1 = prio2 = 0; if (*(char **)p1) { s = strdup(*((char **) p1)); o = get_option(s, __fio_options, &foo); if (o) prio1 = o->prio; free(s); } if (*(char **)p2) { s = strdup(*((char **) p2)); o = get_option(s, __fio_options, &foo); if (o) prio2 = o->prio; free(s); } return prio2 - prio1; } void sort_options(char **opts, struct fio_option *options, int num_opts) { __fio_options = options; qsort(opts, num_opts, sizeof(char *), opt_cmp); __fio_options = NULL; } int parse_cmd_option(const char *opt, const char *val, struct fio_option *options, void *data) { struct fio_option *o; o = find_option(options, opt); if (!o) { log_err("Bad option <%s>\n", opt); return 1; } if (!handle_option(o, val, data)) return 0; log_err("fio: failed parsing %s=%s\n", opt, val); return 1; } int parse_option(char *opt, const char *input, struct fio_option *options, struct fio_option **o, void *data) { char *post; if (!opt) { log_err("fio: failed parsing %s\n", input); *o = NULL; return 1; } *o = get_option(opt, options, &post); if (!*o) { if (post) { int len = strlen(opt); if (opt + len + 1 != post) memmove(opt + len + 1, post, strlen(post)); opt[len] = '='; } return 1; } if (!handle_option(*o, post, data)) return 0; log_err("fio: failed parsing %s\n", input); return 1; } /* * Option match, levenshtein distance. Handy for not quite remembering what * the option name is. */ static int string_distance(const char *s1, const char *s2) { unsigned int s1_len = strlen(s1); unsigned int s2_len = strlen(s2); unsigned int *p, *q, *r; unsigned int i, j; p = malloc(sizeof(unsigned int) * (s2_len + 1)); q = malloc(sizeof(unsigned int) * (s2_len + 1)); p[0] = 0; for (i = 1; i <= s2_len; i++) p[i] = p[i - 1] + 1; for (i = 1; i <= s1_len; i++) { q[0] = p[0] + 1; for (j = 1; j <= s2_len; j++) { unsigned int sub = p[j - 1]; if (s1[i - 1] != s2[j - 1]) sub++; q[j] = min(p[j] + 1, min(q[j - 1] + 1, sub)); } r = p; p = q; q = r; } i = p[s2_len]; free(p); free(q); return i; } static struct fio_option *find_child(struct fio_option *options, struct fio_option *o) { struct fio_option *__o; for (__o = options + 1; __o->name; __o++) if (__o->parent && !strcmp(__o->parent, o->name)) return __o; return NULL; } static void __print_option(struct fio_option *o, struct fio_option *org, int level) { char name[256], *p; int depth; if (!o) return; if (!org) org = o; p = name; depth = level; while (depth--) p += sprintf(p, "%s", " "); sprintf(p, "%s", o->name); log_info("%-24s: %s\n", name, o->help); } static void print_option(struct fio_option *o) { struct fio_option *parent; struct fio_option *__o; unsigned int printed; unsigned int level; __print_option(o, NULL, 0); parent = o; level = 0; do { level++; printed = 0; while ((__o = find_child(o, parent)) != NULL) { __print_option(__o, o, level); o = __o; printed++; } parent = o; } while (printed); } int show_cmd_help(struct fio_option *options, const char *name) { struct fio_option *o, *closest; unsigned int best_dist = -1U; int found = 0; int show_all = 0; if (!name || !strcmp(name, "all")) show_all = 1; closest = NULL; best_dist = -1; for (o = &options[0]; o->name; o++) { int match = 0; if (o->type == FIO_OPT_DEPRECATED) continue; if (!exec_profile && o->prof_name) continue; if (exec_profile && !(o->prof_name && !strcmp(exec_profile, o->prof_name))) continue; if (name) { if (!strcmp(name, o->name) || (o->alias && !strcmp(name, o->alias))) match = 1; else { unsigned int dist; dist = string_distance(name, o->name); if (dist < best_dist) { best_dist = dist; closest = o; } } } if (show_all || match) { found = 1; if (match) log_info("%20s: %s\n", o->name, o->help); if (show_all) { if (!o->parent) print_option(o); continue; } } if (!match) continue; show_option_help(o, 0); } if (found) return 0; log_err("No such command: %s", name); /* * Only print an appropriately close option, one where the edit * distance isn't too big. Otherwise we get crazy matches. */ if (closest && best_dist < 3) { log_info(" - showing closest match\n"); log_info("%20s: %s\n", closest->name, closest->help); show_option_help(closest, 0); } else log_info("\n"); return 1; } /* * Handle parsing of default parameters. */ void fill_default_options(void *data, struct fio_option *options) { struct fio_option *o; dprint(FD_PARSE, "filling default options\n"); for (o = &options[0]; o->name; o++) if (o->def) handle_option(o, o->def, data); } void option_init(struct fio_option *o) { if (o->type == FIO_OPT_DEPRECATED) return; if (o->type == FIO_OPT_BOOL) { o->minval = 0; o->maxval = 1; } if (o->type == FIO_OPT_INT) { if (!o->maxval) o->maxval = UINT_MAX; } if (o->type == FIO_OPT_FLOAT_LIST) { o->minfp = DBL_MIN; o->maxfp = DBL_MAX; } if (o->type == FIO_OPT_STR_SET && o->def) { log_err("Option %s: string set option with" " default will always be true\n", o->name); } if (!o->cb && (!o->off1 && !o->roff1)) log_err("Option %s: neither cb nor offset given\n", o->name); if (!o->category) { log_info("Option %s: no category defined. Setting to misc\n", o->name); o->category = FIO_OPT_C_GENERAL; o->group = FIO_OPT_G_INVALID; } if (o->type == FIO_OPT_STR || o->type == FIO_OPT_STR_STORE || o->type == FIO_OPT_STR_MULTI) return; if (o->cb && ((o->off1 || o->off2 || o->off3 || o->off4) || (o->roff1 || o->roff2 || o->roff3 || o->roff4))) { log_err("Option %s: both cb and offset given\n", o->name); } } /* * Sanitize the options structure. For now it just sets min/max for bool * values and whether both callback and offsets are given. */ void options_init(struct fio_option *options) { struct fio_option *o; dprint(FD_PARSE, "init options\n"); for (o = &options[0]; o->name; o++) { option_init(o); if (o->inverse) o->inv_opt = find_option(options, o->inverse); } } void options_free(struct fio_option *options, void *data) { struct fio_option *o; char **ptr; dprint(FD_PARSE, "free options\n"); for (o = &options[0]; o->name; o++) { if (o->type != FIO_OPT_STR_STORE || !o->off1) continue; ptr = td_var(data, o->off1); if (*ptr) { free(*ptr); *ptr = NULL; } } } fio-2.1.3/parse.h000066400000000000000000000064071222032232000135500ustar00rootroot00000000000000#ifndef FIO_PARSE_H #define FIO_PARSE_H #include "flist.h" /* * Option types */ enum fio_opt_type { FIO_OPT_INVALID = 0, FIO_OPT_STR, FIO_OPT_STR_MULTI, FIO_OPT_STR_VAL, FIO_OPT_STR_VAL_TIME, FIO_OPT_STR_STORE, FIO_OPT_RANGE, FIO_OPT_INT, FIO_OPT_BOOL, FIO_OPT_FLOAT_LIST, FIO_OPT_STR_SET, FIO_OPT_DEPRECATED, }; /* * Match a possible value string with the integer option. */ struct value_pair { const char *ival; /* string option */ unsigned int oval; /* output value */ const char *help; /* help text for sub option */ int or; /* OR value */ void *cb; /* sub-option callback */ }; #define OPT_LEN_MAX 4096 #define PARSE_MAX_VP 24 /* * Option define */ struct fio_option { const char *name; /* option name */ const char *lname; /* long option name */ const char *alias; /* possible old allowed name */ enum fio_opt_type type; /* option type */ unsigned int off1; /* potential parameters */ unsigned int off2; unsigned int off3; unsigned int off4; unsigned int off5; unsigned int off6; void *roff1, *roff2, *roff3, *roff4, *roff5, *roff6; unsigned int maxval; /* max and min value */ int minval; double maxfp; /* max and min floating value */ double minfp; unsigned int interval; /* client hint for suitable interval */ unsigned int maxlen; /* max length */ int neg; /* negate value stored */ int prio; void *cb; /* callback */ const char *help; /* help text for option */ const char *def; /* default setting */ struct value_pair posval[PARSE_MAX_VP];/* possible values */ const char *parent; /* parent option */ int hide; /* hide if parent isn't set */ int hide_on_set; /* hide on set, not on unset */ const char *inverse; /* if set, apply opposite action to this option */ struct fio_option *inv_opt; /* cached lookup */ int (*verify)(struct fio_option *, void *); const char *prof_name; /* only valid for specific profile */ unsigned int category; /* what type of option */ unsigned int group; /* who to group with */ void *gui_data; }; typedef int (str_cb_fn)(void *, char *); extern int parse_option(char *, const char *, struct fio_option *, struct fio_option **, void *); extern void sort_options(char **, struct fio_option *, int); extern int parse_cmd_option(const char *t, const char *l, struct fio_option *, void *); extern int show_cmd_help(struct fio_option *, const char *); extern void fill_default_options(void *, struct fio_option *); extern void option_init(struct fio_option *); extern void options_init(struct fio_option *); extern void options_free(struct fio_option *, void *); extern void strip_blank_front(char **); extern void strip_blank_end(char *); extern int str_to_decimal(const char *, long long *, int, void *); extern int check_str_bytes(const char *p, long long *val, void *data); extern int check_str_time(const char *p, long long *val); extern int str_to_float(const char *str, double *val); /* * Handlers for the options */ typedef int (fio_opt_str_fn)(void *, const char *); typedef int (fio_opt_str_val_fn)(void *, long long *); typedef int (fio_opt_int_fn)(void *, int *); typedef int (fio_opt_str_set_fn)(void *); #define td_var(start, offset) ((void *) start + (offset)) static inline int parse_is_percent(unsigned long long val) { return val <= -1ULL && val >= (-1ULL - 100ULL); } #endif fio-2.1.3/printing.c000066400000000000000000000103661222032232000142620ustar00rootroot00000000000000#include #include #include "gfio.h" #include "cairo_text_helpers.h" #include "printing.h" static struct printing_parameters { gdouble width, height, xdpi, ydpi; GtkPrintSettings *settings; GtkPageSetup *page_setup; } print_params = { 0 }; static void begin_print(GtkPrintOperation *operation, GtkPrintContext *context, gpointer data) { print_params.page_setup = gtk_print_context_get_page_setup(context); print_params.width = gtk_print_context_get_width(context); print_params.height = gtk_print_context_get_height(context); print_params.xdpi = gtk_print_context_get_dpi_x(context); print_params.ydpi = gtk_print_context_get_dpi_y(context); /* assume 1 page for now. */ gtk_print_operation_set_n_pages(operation, 1); } static void results_draw_page(GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr, gpointer data) { cairo_t *cr; char str[20]; double x, y; cr = gtk_print_context_get_cairo_context(context); cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width(cr, 5.0); cairo_move_to(cr, 0.0, 0.0); cairo_line_to(cr, print_params.width, print_params.height); cairo_move_to(cr, 0.0, print_params.height); cairo_line_to(cr, print_params.width, 0.0); cairo_stroke(cr); x = print_params.width / 4.0; y = print_params.height / 5.0; sprintf(str, "(%g,%g)", x, y); draw_right_justified_text(cr, "Sans", x, y, 12.0, str); cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width(cr, 2.0); cairo_move_to(cr, x, y - 30.0); cairo_line_to(cr, x, y + 30.0); cairo_move_to(cr, x - 30, y); cairo_line_to(cr, x + 30, y); y *= 4.0; x *= 2.0; sprintf(str, "(%g,%g)", x, y); draw_right_justified_text(cr, "Sans", x, y, 12.0, str); cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width(cr, 2.0); cairo_move_to(cr, x, y - 30.0); cairo_line_to(cr, x, y + 30.0); cairo_move_to(cr, x - 30, y); cairo_line_to(cr, x + 30, y); cairo_stroke(cr); } static void printing_error_dialog(GtkWidget *window, GError *print_error) { GtkWidget *error_dialog; printf("printing_error_dialog called\n"); printf("error message = %s\n", print_error->message); error_dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Print error:\n%s", print_error->message); g_signal_connect(error_dialog, "response", G_CALLBACK(gtk_widget_destroy), NULL); gtk_widget_show(error_dialog); } static void results_print_done(GtkPrintOperation *operation, GtkPrintOperationResult result, gpointer data) { GError *print_error; struct gui_entry *ge = data; if (result != GTK_PRINT_OPERATION_RESULT_ERROR) return; gtk_print_operation_get_error(operation, &print_error); printing_error_dialog(ge->results_window, print_error); g_error_free(print_error); } void gfio_print_results(struct gui_entry *ge) { GtkPrintOperation *print; GtkPrintOperationResult res; GError *print_error; print = gtk_print_operation_new(); if (print_params.settings != NULL) gtk_print_operation_set_print_settings(print, print_params.settings); if (print_params.page_setup != NULL) gtk_print_operation_set_default_page_setup(print, print_params.page_setup); g_signal_connect(print, "begin_print", G_CALLBACK(begin_print), NULL); g_signal_connect(print, "draw_page", G_CALLBACK(results_draw_page), NULL); g_signal_connect(print, "done", G_CALLBACK(results_print_done), NULL); gtk_print_operation_set_allow_async(print, TRUE); res = gtk_print_operation_run(print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, GTK_WINDOW(ge->results_window), &print_error); /* * Something's not quite right about the error handling. If I print * to a file, and the file exists, and I don't have write permission * on that file but attempt to replace it anyway, then it just kind of * hangs and I don't get into any of this error handling stuff at all, * neither here, nor in results_print_done(). */ if (res == GTK_PRINT_OPERATION_RESULT_ERROR) { printing_error_dialog(ge->results_window, print_error); g_error_free(print_error); } else { if (res == GTK_PRINT_OPERATION_RESULT_APPLY) { if (print_params.settings != NULL) g_object_unref(print_params.settings); print_params.settings = g_object_ref(gtk_print_operation_get_print_settings(print)); } } g_object_unref(print); } fio-2.1.3/printing.h000066400000000000000000000001361222032232000142610ustar00rootroot00000000000000#ifndef PRINTING_H #define PRINTING_H void gfio_print_results(struct gui_entry *ge); #endif fio-2.1.3/profile.c000066400000000000000000000041351222032232000140650ustar00rootroot00000000000000#include "fio.h" #include "profile.h" #include "debug.h" #include "flist.h" #include "options.h" static FLIST_HEAD(profile_list); struct profile_ops *find_profile(const char *profile) { struct profile_ops *ops = NULL; struct flist_head *n; flist_for_each(n, &profile_list) { ops = flist_entry(n, struct profile_ops, list); if (!strcmp(profile, ops->name)) break; ops = NULL; } return ops; } int load_profile(const char *profile) { struct profile_ops *ops; dprint(FD_PROFILE, "loading profile '%s'\n", profile); ops = find_profile(profile); if (ops) { if (ops->prep_cmd()) { log_err("fio: profile %s prep failed\n", profile); return 1; } add_job_opts(ops->cmdline, FIO_CLIENT_TYPE_CLI); return 0; } log_err("fio: profile '%s' not found\n", profile); return 1; } static int add_profile_options(struct profile_ops *ops) { struct fio_option *o; if (!ops->options) return 0; o = ops->options; while (o->name) { o->prof_name = ops->name; if (add_option(o)) return 1; o++; } return 0; } int register_profile(struct profile_ops *ops) { int ret; dprint(FD_PROFILE, "register profile '%s'\n", ops->name); ret = add_profile_options(ops); if (!ret) { flist_add_tail(&ops->list, &profile_list); add_opt_posval("profile", ops->name, ops->desc); return 0; } invalidate_profile_options(ops->name); return ret; } void unregister_profile(struct profile_ops *ops) { dprint(FD_PROFILE, "unregister profile '%s'\n", ops->name); flist_del(&ops->list); invalidate_profile_options(ops->name); del_opt_posval("profile", ops->name); } void profile_add_hooks(struct thread_data *td) { struct profile_ops *ops; if (!exec_profile) return; ops = find_profile(exec_profile); if (!ops) return; if (ops->io_ops) { td->prof_io_ops = *ops->io_ops; td->flags |= TD_F_PROFILE_OPS; } } int profile_td_init(struct thread_data *td) { struct prof_io_ops *ops = &td->prof_io_ops; if (ops->td_init) return ops->td_init(td); return 0; } void profile_td_exit(struct thread_data *td) { struct prof_io_ops *ops = &td->prof_io_ops; if (ops->td_exit) ops->td_exit(td); } fio-2.1.3/profile.h000066400000000000000000000021761222032232000140750ustar00rootroot00000000000000#ifndef FIO_PROFILE_H #define FIO_PROFILE_H #include "flist.h" /* * Functions for overriding internal fio io_u functions */ struct prof_io_ops { int (*td_init)(struct thread_data *); void (*td_exit)(struct thread_data *); int (*fill_io_u_off)(struct thread_data *, struct io_u *, unsigned int *); int (*fill_io_u_size)(struct thread_data *, struct io_u *, unsigned int); struct fio_file *(*get_next_file)(struct thread_data *); int (*io_u_lat)(struct thread_data *, uint64_t); }; struct profile_ops { struct flist_head list; char name[32]; char desc[64]; int flags; /* * Profile specific options */ struct fio_option *options; /* * Called after parsing options, to prepare 'cmdline' */ int (*prep_cmd)(void); /* * The complete command line */ const char **cmdline; struct prof_io_ops *io_ops; }; int register_profile(struct profile_ops *); void unregister_profile(struct profile_ops *); int load_profile(const char *); struct profile_ops *find_profile(const char *); void profile_add_hooks(struct thread_data *); int profile_td_init(struct thread_data *); void profile_td_exit(struct thread_data *); #endif fio-2.1.3/profiles/000077500000000000000000000000001222032232000141015ustar00rootroot00000000000000fio-2.1.3/profiles/act.c000066400000000000000000000236171222032232000150250ustar00rootroot00000000000000#include "../fio.h" #include "../profile.h" #include "../parse.h" /* * 1x loads */ #define R_LOAD 2000 #define W_LOAD 1000 #define SAMPLE_SEC 3600 /* 1h checks */ struct act_pass_criteria { unsigned int max_usec; unsigned int max_perm; }; #define ACT_MAX_CRIT 3 static struct act_pass_criteria act_pass[ACT_MAX_CRIT] = { { .max_usec = 1000, .max_perm = 50, }, { .max_usec = 8000, .max_perm = 10, }, { .max_usec = 64000, .max_perm = 1, }, }; struct act_slice { uint64_t lat_buckets[ACT_MAX_CRIT]; uint64_t total_ios; }; struct act_run_data { struct fio_mutex *mutex; unsigned int pending; struct act_slice *slices; unsigned int nr_slices; }; static struct act_run_data *act_run_data; struct act_prof_data { struct timeval sample_tv; struct act_slice *slices; unsigned int cur_slice; unsigned int nr_slices; }; static char *device_names; static unsigned int load; static unsigned int prep; static unsigned int threads_per_queue; static unsigned int num_read_blocks; static unsigned int write_size; static unsigned long long test_duration; #define ACT_MAX_OPTS 128 static const char *act_opts[ACT_MAX_OPTS] = { "direct=1", "ioengine=sync", "random_generator=lfsr", "group_reporting=1", "thread", NULL, }; static unsigned int opt_idx = 5; static unsigned int org_idx; static int act_add_opt(const char *format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); static struct fio_option options[] = { { .name = "device-names", .lname = "device-names", .type = FIO_OPT_STR_STORE, .roff1 = &device_names, .help = "Devices to use", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_ACT, }, { .name = "load", .lname = "Load multiplier", .type = FIO_OPT_INT, .roff1 = &load, .help = "ACT load multipler (default 1x)", .def = "1", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_ACT, }, { .name = "test-duration", .lname = "Test duration", .type = FIO_OPT_STR_VAL_TIME, .roff1 = &test_duration, .help = "How long the entire test takes to run", .def = "24h", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_ACT, }, { .name = "threads-per-queue", .lname = "Number of read IO threads per device", .type = FIO_OPT_INT, .roff1 = &threads_per_queue, .help = "Number of read IO threads per device", .def = "8", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_ACT, }, { .name = "read-req-num-512-blocks", .lname = "Number of 512b blocks to read", .type = FIO_OPT_INT, .roff1 = &num_read_blocks, .help = "Number of 512b blocks to read at the time", .def = "3", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_ACT, }, { .name = "large-block-op-kbytes", .lname = "Size of large block ops (writes)", .type = FIO_OPT_INT, .roff1 = &write_size, .help = "Size of large block ops (writes)", .def = "128k", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_ACT, }, { .name = "prep", .lname = "Run ACT prep phase", .type = FIO_OPT_STR_SET, .roff1 = &prep, .help = "Set to run ACT prep phase", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_ACT, }, { .name = NULL, }, }; static int act_add_opt(const char *str, ...) { char buffer[512]; va_list args; size_t len; if (opt_idx == ACT_MAX_OPTS) { log_err("act: ACT_MAX_OPTS is too small\n"); return 1; } va_start(args, str); len = vsnprintf(buffer, sizeof(buffer), str, args); va_end(args); if (len) act_opts[opt_idx++] = strdup(buffer); return 0; } static int act_add_rw(const char *dev, int reads) { if (act_add_opt("name=act-%s-%s", reads ? "read" : "write", dev)) return 1; if (act_add_opt("filename=%s", dev)) return 1; if (act_add_opt("rw=%s", reads ? "randread" : "randwrite")) return 1; if (reads) { int rload = load * R_LOAD / threads_per_queue; if (act_add_opt("numjobs=%u", threads_per_queue)) return 1; if (act_add_opt("rate_iops=%u", rload)) return 1; if (act_add_opt("bs=%u", num_read_blocks * 512)) return 1; } else { const int rsize = write_size / (num_read_blocks * 512); int wload = (load * W_LOAD + rsize - 1) / rsize; if (act_add_opt("rate_iops=%u", wload)) return 1; if (act_add_opt("bs=%u", write_size)) return 1; } return 0; } static int act_add_dev_prep(const char *dev) { /* Add sequential zero phase */ if (act_add_opt("name=act-prep-zeroes-%s", dev)) return 1; if (act_add_opt("filename=%s", dev)) return 1; if (act_add_opt("bs=1M")) return 1; if (act_add_opt("zero_buffers")) return 1; if (act_add_opt("rw=write")) return 1; /* Randomly overwrite device */ if (act_add_opt("name=act-prep-salt-%s", dev)) return 1; if (act_add_opt("stonewall")) return 1; if (act_add_opt("filename=%s", dev)) return 1; if (act_add_opt("bs=4k")) return 1; if (act_add_opt("ioengine=libaio")) return 1; if (act_add_opt("iodepth=64")) return 1; if (act_add_opt("rw=randwrite")) return 1; return 0; } static int act_add_dev(const char *dev) { if (prep) return act_add_dev_prep(dev); if (act_add_opt("runtime=%llus", test_duration)) return 1; if (act_add_opt("time_based=1")) return 1; if (act_add_rw(dev, 1)) return 1; if (act_add_rw(dev, 0)) return 1; return 0; } /* * Fill our private options into the command line */ static int act_prep_cmdline(void) { if (!device_names) { log_err("act: you need to set IO target(s) with the " "device-names option.\n"); return 1; } org_idx = opt_idx; do { char *dev; dev = strsep(&device_names, ","); if (!dev) break; if (act_add_dev(dev)) { log_err("act: failed adding device to the mix\n"); break; } } while (1); return 0; } static int act_io_u_lat(struct thread_data *td, uint64_t usec) { struct act_prof_data *apd = td->prof_data; struct act_slice *slice; int i, ret = 0; double perm; if (prep) return 0; /* * Really should not happen, but lets not let jitter at the end * ruin our day. */ if (apd->cur_slice >= apd->nr_slices) return 0; slice = &apd->slices[apd->cur_slice]; slice->total_ios++; for (i = ACT_MAX_CRIT - 1; i >= 0; i--) { if (usec > act_pass[i].max_usec) { slice->lat_buckets[i]++; break; } } if (time_since_now(&apd->sample_tv) < SAMPLE_SEC) return 0; /* SAMPLE_SEC has passed, check criteria for pass */ for (i = 0; i < ACT_MAX_CRIT; i++) { perm = (1000.0 * slice->lat_buckets[i]) / slice->total_ios; if (perm < act_pass[i].max_perm) continue; log_err("act: %f%% exceeds pass criteria of %f%%\n", perm / 10.0, (double) act_pass[i].max_perm / 10.0); ret = 1; break; } fio_gettime(&apd->sample_tv, NULL); apd->cur_slice++; return ret; } static void get_act_ref(void) { fio_mutex_down(act_run_data->mutex); act_run_data->pending++; fio_mutex_up(act_run_data->mutex); } static int show_slice(struct act_slice *slice, unsigned int slice_num) { unsigned int i, failed = 0; log_info(" %2u", slice_num); for (i = 0; i < ACT_MAX_CRIT; i++) { double perc = 0.0; if (slice->total_ios) perc = 100.0 * (double) slice->lat_buckets[i] / (double) slice->total_ios; if ((perc * 10.0) >= act_pass[i].max_perm) failed++; log_info("\t%2.2f", perc); } for (i = 0; i < ACT_MAX_CRIT; i++) { double perc = 0.0; if (slice->total_ios) perc = 100.0 * (double) slice->lat_buckets[i] / (double) slice->total_ios; log_info("\t%2.2f", perc); } log_info("\n"); return failed; } static void act_show_all_stats(void) { unsigned int i, fails = 0; log_info(" trans device\n"); log_info(" %%>(ms) %%>(ms)\n"); log_info(" slice"); for (i = 0; i < ACT_MAX_CRIT; i++) log_info("\t %2u", act_pass[i].max_usec / 1000); for (i = 0; i < ACT_MAX_CRIT; i++) log_info("\t %2u", act_pass[i].max_usec / 1000); log_info("\n"); log_info(" ----- ----- ----- ------ ----- ----- ------\n"); for (i = 0; i < act_run_data->nr_slices; i++) fails += show_slice(&act_run_data->slices[i], i + 1); log_info("\nact: test complete, device(s): %s\n", fails ? "FAILED" : "PASSED"); } static void put_act_ref(struct thread_data *td) { struct act_prof_data *apd = td->prof_data; unsigned int i, slice; fio_mutex_down(act_run_data->mutex); if (!act_run_data->slices) { act_run_data->slices = calloc(sizeof(struct act_slice), apd->nr_slices); act_run_data->nr_slices = apd->nr_slices; } for (slice = 0; slice < apd->nr_slices; slice++) { struct act_slice *dst = &act_run_data->slices[slice]; struct act_slice *src = &apd->slices[slice]; dst->total_ios += src->total_ios; for (i = 0; i < ACT_MAX_CRIT; i++) dst->lat_buckets[i] += src->lat_buckets[i]; } if (!--act_run_data->pending) act_show_all_stats(); fio_mutex_up(act_run_data->mutex); } static int act_td_init(struct thread_data *td) { struct act_prof_data *apd; unsigned int nr_slices; get_act_ref(); apd = calloc(sizeof(*apd), 1); nr_slices = (test_duration + SAMPLE_SEC - 1) / SAMPLE_SEC; apd->slices = calloc(sizeof(struct act_slice), nr_slices); apd->nr_slices = nr_slices; fio_gettime(&apd->sample_tv, NULL); td->prof_data = apd; return 0; } static void act_td_exit(struct thread_data *td) { struct act_prof_data *apd = td->prof_data; put_act_ref(td); free(apd->slices); free(apd); td->prof_data = NULL; } static struct prof_io_ops act_io_ops = { .td_init = act_td_init, .td_exit = act_td_exit, .io_u_lat = act_io_u_lat, }; static struct profile_ops act_profile = { .name = "act", .desc = "ACT Aerospike like benchmark", .options = options, .prep_cmd = act_prep_cmdline, .cmdline = act_opts, .io_ops = &act_io_ops, }; static void fio_init act_register(void) { act_run_data = calloc(sizeof(*act_run_data), 1); act_run_data->mutex = fio_mutex_init(FIO_MUTEX_UNLOCKED); if (register_profile(&act_profile)) log_err("fio: failed to register profile 'act'\n"); } static void fio_exit act_unregister(void) { while (org_idx && org_idx < opt_idx) free((void *) act_opts[++org_idx]); unregister_profile(&act_profile); fio_mutex_remove(act_run_data->mutex); free(act_run_data->slices); free(act_run_data); act_run_data = NULL; } fio-2.1.3/profiles/tiobench.c000066400000000000000000000051631222032232000160450ustar00rootroot00000000000000#include "../fio.h" #include "../profile.h" #include "../parse.h" static unsigned long long size; static unsigned int loops = 1; static unsigned int bs = 4096; static unsigned int nthreads = 1; static char *dir; char sz_idx[80], bs_idx[80], loop_idx[80], dir_idx[80], t_idx[80]; static const char *tb_opts[] = { "buffered=0", sz_idx, bs_idx, loop_idx, dir_idx, t_idx, "timeout=600", "group_reporting", "thread", "overwrite=1", "filename=.fio.tio.1:.fio.tio.2:.fio.tio.3:.fio.tio.4", "ioengine=sync", "name=seqwrite", "rw=write", "end_fsync=1", "name=randwrite", "stonewall", "rw=randwrite", "end_fsync=1", "name=seqread", "stonewall", "rw=read", "name=randread", "stonewall", "rw=randread", NULL, }; static struct fio_option options[] = { { .name = "size", .lname = "Tiobench size", .type = FIO_OPT_STR_VAL, .roff1 = &size, .help = "Size in MB", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_TIOBENCH, }, { .name = "block", .lname = "Tiobench block", .type = FIO_OPT_INT, .roff1 = &bs, .help = "Block size in bytes", .def = "4k", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_TIOBENCH, }, { .name = "numruns", .lname = "Tiobench numruns", .type = FIO_OPT_INT, .roff1 = &loops, .help = "Number of runs", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_TIOBENCH, }, { .name = "dir", .lname = "Tiobench directory", .type = FIO_OPT_STR_STORE, .roff1 = &dir, .help = "Test directory", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_TIOBENCH, }, { .name = "threads", .lname = "Tiobench threads", .type = FIO_OPT_INT, .roff1 = &nthreads, .help = "Number of Threads", .category = FIO_OPT_C_PROFILE, .group = FIO_OPT_G_TIOBENCH, }, { .name = NULL, }, }; /* * Fill our private options into the command line */ static int tb_prep_cmdline(void) { /* * tiobench uses size as MB, so multiply up */ size *= 1024 * 1024ULL; if (size) sprintf(sz_idx, "size=%llu", size); else strcpy(sz_idx, "size=4*1024*$mb_memory"); sprintf(bs_idx, "bs=%u", bs); sprintf(loop_idx, "loops=%u", loops); if (dir) sprintf(dir_idx, "directory=%s", dir); else sprintf(dir_idx, "directory=./"); sprintf(t_idx, "numjobs=%u", nthreads); return 0; } static struct profile_ops tiobench_profile = { .name = "tiobench", .desc = "tiotest/tiobench benchmark", .options = options, .prep_cmd = tb_prep_cmdline, .cmdline = tb_opts, }; static void fio_init tiobench_register(void) { if (register_profile(&tiobench_profile)) log_err("fio: failed to register profile 'tiobench'\n"); } static void fio_exit tiobench_unregister(void) { unregister_profile(&tiobench_profile); } fio-2.1.3/server.c000066400000000000000000001055541222032232000137420ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_ZLIB #include #endif #include "fio.h" #include "server.h" #include "crc/crc16.h" #include "lib/ieee754.h" int fio_net_port = FIO_NET_PORT; int exit_backend = 0; static int server_fd = -1; static char *fio_server_arg; static char *bind_sock; static struct sockaddr_in saddr_in; static struct sockaddr_in6 saddr_in6; static int use_ipv6; #ifdef CONFIG_ZLIB static unsigned int has_zlib = 1; #else static unsigned int has_zlib = 0; #endif static unsigned int use_zlib; struct fio_fork_item { struct flist_head list; int exitval; int signal; int exited; pid_t pid; }; /* Created on fork on new connection */ static FLIST_HEAD(conn_list); /* Created on job fork from connection */ static FLIST_HEAD(job_list); static const char *fio_server_ops[FIO_NET_CMD_NR] = { "", "QUIT", "EXIT", "JOB", "JOBLINE", "TEXT", "TS", "GS", "SEND_ETA", "ETA", "PROBE", "START", "STOP", "DISK_UTIL", "SERVER_START", "ADD_JOB", "CMD_RUN" "CMD_IOLOG", }; const char *fio_server_op(unsigned int op) { static char buf[32]; if (op < FIO_NET_CMD_NR) return fio_server_ops[op]; sprintf(buf, "UNKNOWN/%d", op); return buf; } static ssize_t iov_total_len(const struct iovec *iov, int count) { ssize_t ret = 0; while (count--) { ret += iov->iov_len; iov++; } return ret; } static int fio_sendv_data(int sk, struct iovec *iov, int count) { ssize_t total_len = iov_total_len(iov, count); ssize_t ret; do { ret = writev(sk, iov, count); if (ret > 0) { total_len -= ret; if (!total_len) break; while (ret) { if (ret >= iov->iov_len) { ret -= iov->iov_len; iov++; continue; } iov->iov_base += ret; iov->iov_len -= ret; ret = 0; } } else if (!ret) break; else if (errno == EAGAIN || errno == EINTR) continue; else break; } while (!exit_backend); if (!total_len) return 0; if (errno) return -errno; return 1; } int fio_send_data(int sk, const void *p, unsigned int len) { struct iovec iov = { .iov_base = (void *) p, .iov_len = len }; assert(len <= sizeof(struct fio_net_cmd) + FIO_SERVER_MAX_FRAGMENT_PDU); return fio_sendv_data(sk, &iov, 1); } int fio_recv_data(int sk, void *p, unsigned int len) { do { int ret = recv(sk, p, len, MSG_WAITALL); if (ret > 0) { len -= ret; if (!len) break; p += ret; continue; } else if (!ret) break; else if (errno == EAGAIN || errno == EINTR) continue; else break; } while (!exit_backend); if (!len) return 0; return -1; } static int verify_convert_cmd(struct fio_net_cmd *cmd) { uint16_t crc; cmd->cmd_crc16 = le16_to_cpu(cmd->cmd_crc16); cmd->pdu_crc16 = le16_to_cpu(cmd->pdu_crc16); crc = fio_crc16(cmd, FIO_NET_CMD_CRC_SZ); if (crc != cmd->cmd_crc16) { log_err("fio: server bad crc on command (got %x, wanted %x)\n", cmd->cmd_crc16, crc); return 1; } cmd->version = le16_to_cpu(cmd->version); cmd->opcode = le16_to_cpu(cmd->opcode); cmd->flags = le32_to_cpu(cmd->flags); cmd->tag = le64_to_cpu(cmd->tag); cmd->pdu_len = le32_to_cpu(cmd->pdu_len); switch (cmd->version) { case FIO_SERVER_VER: break; default: log_err("fio: bad server cmd version %d\n", cmd->version); return 1; } if (cmd->pdu_len > FIO_SERVER_MAX_FRAGMENT_PDU) { log_err("fio: command payload too large: %u\n", cmd->pdu_len); return 1; } return 0; } /* * Read (and defragment, if necessary) incoming commands */ struct fio_net_cmd *fio_net_recv_cmd(int sk) { struct fio_net_cmd cmd, *cmdret = NULL; size_t cmd_size = 0, pdu_offset = 0; uint16_t crc; int ret, first = 1; void *pdu = NULL; do { ret = fio_recv_data(sk, &cmd, sizeof(cmd)); if (ret) break; /* We have a command, verify it and swap if need be */ ret = verify_convert_cmd(&cmd); if (ret) break; if (first) { /* if this is text, add room for \0 at the end */ cmd_size = sizeof(cmd) + cmd.pdu_len + 1; assert(!cmdret); } else cmd_size += cmd.pdu_len; cmdret = realloc(cmdret, cmd_size); if (first) memcpy(cmdret, &cmd, sizeof(cmd)); else if (cmdret->opcode != cmd.opcode) { log_err("fio: fragment opcode mismatch (%d != %d)\n", cmdret->opcode, cmd.opcode); ret = 1; break; } if (!cmd.pdu_len) break; /* There's payload, get it */ pdu = (void *) cmdret->payload + pdu_offset; ret = fio_recv_data(sk, pdu, cmd.pdu_len); if (ret) break; /* Verify payload crc */ crc = fio_crc16(pdu, cmd.pdu_len); if (crc != cmd.pdu_crc16) { log_err("fio: server bad crc on payload "); log_err("(got %x, wanted %x)\n", cmd.pdu_crc16, crc); ret = 1; break; } pdu_offset += cmd.pdu_len; if (!first) cmdret->pdu_len += cmd.pdu_len; first = 0; } while (cmd.flags & FIO_NET_CMD_F_MORE); if (ret) { free(cmdret); cmdret = NULL; } else if (cmdret) { /* zero-terminate text input */ if (cmdret->pdu_len) { if (cmdret->opcode == FIO_NET_CMD_TEXT) { struct cmd_text_pdu *pdu = (struct cmd_text_pdu *) cmdret->payload; char *buf = (char *) pdu->buf; buf[pdu->buf_len] = '\0'; } else if (cmdret->opcode == FIO_NET_CMD_JOB) { struct cmd_job_pdu *pdu = (struct cmd_job_pdu *) cmdret->payload; char *buf = (char *) pdu->buf; int len = le32_to_cpu(pdu->buf_len); buf[len] = '\0'; } } /* frag flag is internal */ cmdret->flags &= ~FIO_NET_CMD_F_MORE; } return cmdret; } static void add_reply(uint64_t tag, struct flist_head *list) { struct fio_net_cmd_reply *reply; reply = (struct fio_net_cmd_reply *) (uintptr_t) tag; flist_add_tail(&reply->list, list); } static uint64_t alloc_reply(uint64_t tag, uint16_t opcode) { struct fio_net_cmd_reply *reply; reply = calloc(1, sizeof(*reply)); INIT_FLIST_HEAD(&reply->list); gettimeofday(&reply->tv, NULL); reply->saved_tag = tag; reply->opcode = opcode; return (uintptr_t) reply; } static void free_reply(uint64_t tag) { struct fio_net_cmd_reply *reply; reply = (struct fio_net_cmd_reply *) (uintptr_t) tag; free(reply); } void fio_net_cmd_crc_pdu(struct fio_net_cmd *cmd, const void *pdu) { uint32_t pdu_len; cmd->cmd_crc16 = __cpu_to_le16(fio_crc16(cmd, FIO_NET_CMD_CRC_SZ)); pdu_len = le32_to_cpu(cmd->pdu_len); cmd->pdu_crc16 = __cpu_to_le16(fio_crc16(pdu, pdu_len)); } void fio_net_cmd_crc(struct fio_net_cmd *cmd) { fio_net_cmd_crc_pdu(cmd, cmd->payload); } int fio_net_send_cmd(int fd, uint16_t opcode, const void *buf, off_t size, uint64_t *tagptr, struct flist_head *list) { struct fio_net_cmd *cmd = NULL; size_t this_len, cur_len = 0; uint64_t tag; int ret; if (list) { assert(tagptr); tag = *tagptr = alloc_reply(*tagptr, opcode); } else tag = tagptr ? *tagptr : 0; do { this_len = size; if (this_len > FIO_SERVER_MAX_FRAGMENT_PDU) this_len = FIO_SERVER_MAX_FRAGMENT_PDU; if (!cmd || cur_len < sizeof(*cmd) + this_len) { if (cmd) free(cmd); cur_len = sizeof(*cmd) + this_len; cmd = malloc(cur_len); } fio_init_net_cmd(cmd, opcode, buf, this_len, tag); if (this_len < size) cmd->flags = __cpu_to_le32(FIO_NET_CMD_F_MORE); fio_net_cmd_crc(cmd); ret = fio_send_data(fd, cmd, sizeof(*cmd) + this_len); size -= this_len; buf += this_len; } while (!ret && size); if (list) { if (ret) free_reply(tag); else add_reply(tag, list); } if (cmd) free(cmd); return ret; } static int fio_net_send_simple_stack_cmd(int sk, uint16_t opcode, uint64_t tag) { struct fio_net_cmd cmd; fio_init_net_cmd(&cmd, opcode, NULL, 0, tag); fio_net_cmd_crc(&cmd); return fio_send_data(sk, &cmd, sizeof(cmd)); } /* * If 'list' is non-NULL, then allocate and store the sent command for * later verification. */ int fio_net_send_simple_cmd(int sk, uint16_t opcode, uint64_t tag, struct flist_head *list) { int ret; if (list) tag = alloc_reply(tag, opcode); ret = fio_net_send_simple_stack_cmd(sk, opcode, tag); if (ret) { if (list) free_reply(tag); return ret; } if (list) add_reply(tag, list); return 0; } int fio_net_send_quit(int sk) { dprint(FD_NET, "server: sending quit\n"); return fio_net_send_simple_cmd(sk, FIO_NET_CMD_QUIT, 0, NULL); } static int fio_net_send_ack(int sk, struct fio_net_cmd *cmd, int error, int signal) { struct cmd_end_pdu epdu; uint64_t tag = 0; if (cmd) tag = cmd->tag; epdu.error = __cpu_to_le32(error); epdu.signal = __cpu_to_le32(signal); return fio_net_send_cmd(sk, FIO_NET_CMD_STOP, &epdu, sizeof(epdu), &tag, NULL); } int fio_net_send_stop(int sk, int error, int signal) { dprint(FD_NET, "server: sending stop (%d, %d)\n", error, signal); return fio_net_send_ack(sk, NULL, error, signal); } static void fio_server_add_fork_item(pid_t pid, struct flist_head *list) { struct fio_fork_item *ffi; ffi = malloc(sizeof(*ffi)); ffi->exitval = 0; ffi->signal = 0; ffi->exited = 0; ffi->pid = pid; flist_add_tail(&ffi->list, list); } static void fio_server_add_conn_pid(pid_t pid) { dprint(FD_NET, "server: forked off connection job (pid=%u)\n", pid); fio_server_add_fork_item(pid, &conn_list); } static void fio_server_add_job_pid(pid_t pid) { dprint(FD_NET, "server: forked off job job (pid=%u)\n", pid); fio_server_add_fork_item(pid, &job_list); } static void fio_server_check_fork_item(struct fio_fork_item *ffi) { int ret, status; ret = waitpid(ffi->pid, &status, WNOHANG); if (ret < 0) { if (errno == ECHILD) { log_err("fio: connection pid %u disappeared\n", ffi->pid); ffi->exited = 1; } else log_err("fio: waitpid: %s\n", strerror(errno)); } else if (ret == ffi->pid) { if (WIFSIGNALED(status)) { ffi->signal = WTERMSIG(status); ffi->exited = 1; } if (WIFEXITED(status)) { if (WEXITSTATUS(status)) ffi->exitval = WEXITSTATUS(status); ffi->exited = 1; } } } static void fio_server_fork_item_done(struct fio_fork_item *ffi) { dprint(FD_NET, "pid %u exited, sig=%u, exitval=%d\n", ffi->pid, ffi->signal, ffi->exitval); /* * Fold STOP and QUIT... */ fio_net_send_stop(server_fd, ffi->exitval, ffi->signal); fio_net_send_quit(server_fd); flist_del(&ffi->list); free(ffi); } static void fio_server_check_fork_items(struct flist_head *list) { struct flist_head *entry, *tmp; struct fio_fork_item *ffi; flist_for_each_safe(entry, tmp, list) { ffi = flist_entry(entry, struct fio_fork_item, list); fio_server_check_fork_item(ffi); if (ffi->exited) fio_server_fork_item_done(ffi); } } static void fio_server_check_jobs(void) { fio_server_check_fork_items(&job_list); } static void fio_server_check_conns(void) { fio_server_check_fork_items(&conn_list); } static int handle_run_cmd(struct fio_net_cmd *cmd) { pid_t pid; int ret; set_genesis_time(); pid = fork(); if (pid) { fio_server_add_job_pid(pid); return 0; } ret = fio_backend(); free_threads_shm(); _exit(ret); } static int handle_job_cmd(struct fio_net_cmd *cmd) { struct cmd_job_pdu *pdu = (struct cmd_job_pdu *) cmd->payload; void *buf = pdu->buf; struct cmd_start_pdu spdu; pdu->buf_len = le32_to_cpu(pdu->buf_len); pdu->client_type = le32_to_cpu(pdu->client_type); if (parse_jobs_ini(buf, 1, 0, pdu->client_type)) { fio_net_send_quit(server_fd); return -1; } spdu.jobs = cpu_to_le32(thread_number); spdu.stat_outputs = cpu_to_le32(stat_number); fio_net_send_cmd(server_fd, FIO_NET_CMD_START, &spdu, sizeof(spdu), NULL, NULL); return 0; } static int handle_jobline_cmd(struct fio_net_cmd *cmd) { void *pdu = cmd->payload; struct cmd_single_line_pdu *cslp; struct cmd_line_pdu *clp; unsigned long offset; struct cmd_start_pdu spdu; char **argv; int i; clp = pdu; clp->lines = le16_to_cpu(clp->lines); clp->client_type = le16_to_cpu(clp->client_type); argv = malloc(clp->lines * sizeof(char *)); offset = sizeof(*clp); dprint(FD_NET, "server: %d command line args\n", clp->lines); for (i = 0; i < clp->lines; i++) { cslp = pdu + offset; argv[i] = (char *) cslp->text; offset += sizeof(*cslp) + le16_to_cpu(cslp->len); dprint(FD_NET, "server: %d: %s\n", i, argv[i]); } if (parse_cmd_line(clp->lines, argv, clp->client_type)) { fio_net_send_quit(server_fd); free(argv); return -1; } free(argv); spdu.jobs = cpu_to_le32(thread_number); spdu.stat_outputs = cpu_to_le32(stat_number); fio_net_send_cmd(server_fd, FIO_NET_CMD_START, &spdu, sizeof(spdu), NULL, NULL); return 0; } static int handle_probe_cmd(struct fio_net_cmd *cmd) { struct cmd_client_probe_pdu *pdu = (struct cmd_client_probe_pdu *) cmd->payload; struct cmd_probe_reply_pdu probe; uint64_t tag = cmd->tag; dprint(FD_NET, "server: sending probe reply\n"); memset(&probe, 0, sizeof(probe)); gethostname((char *) probe.hostname, sizeof(probe.hostname)); #ifdef CONFIG_BIG_ENDIAN probe.bigendian = 1; #endif strncpy((char *) probe.fio_version, fio_version_string, sizeof(probe.fio_version)); probe.os = FIO_OS; probe.arch = FIO_ARCH; probe.bpp = sizeof(void *); probe.cpus = __cpu_to_le32(cpus_online()); /* * If the client supports compression and we do too, then enable it */ if (has_zlib && le64_to_cpu(pdu->flags) & FIO_PROBE_FLAG_ZLIB) { probe.flags = __cpu_to_le64(FIO_PROBE_FLAG_ZLIB); use_zlib = 1; } else { probe.flags = 0; use_zlib = 0; } return fio_net_send_cmd(server_fd, FIO_NET_CMD_PROBE, &probe, sizeof(probe), &tag, NULL); } static int handle_send_eta_cmd(struct fio_net_cmd *cmd) { struct jobs_eta *je; size_t size; uint64_t tag = cmd->tag; int i; if (!thread_number) return 0; size = sizeof(*je) + thread_number * sizeof(char) + 1; je = malloc(size); memset(je, 0, size); if (!calc_thread_status(je, 1)) { free(je); return 0; } dprint(FD_NET, "server sending status\n"); je->nr_running = cpu_to_le32(je->nr_running); je->nr_ramp = cpu_to_le32(je->nr_ramp); je->nr_pending = cpu_to_le32(je->nr_pending); je->nr_setting_up = cpu_to_le32(je->nr_setting_up); je->files_open = cpu_to_le32(je->files_open); for (i = 0; i < DDIR_RWDIR_CNT; i++) { je->m_rate[i] = cpu_to_le32(je->m_rate[i]); je->t_rate[i] = cpu_to_le32(je->t_rate[i]); je->m_iops[i] = cpu_to_le32(je->m_iops[i]); je->t_iops[i] = cpu_to_le32(je->t_iops[i]); je->rate[i] = cpu_to_le32(je->rate[i]); je->iops[i] = cpu_to_le32(je->iops[i]); } je->elapsed_sec = cpu_to_le64(je->elapsed_sec); je->eta_sec = cpu_to_le64(je->eta_sec); je->nr_threads = cpu_to_le32(je->nr_threads); je->is_pow2 = cpu_to_le32(je->is_pow2); je->unit_base = cpu_to_le32(je->unit_base); fio_net_send_cmd(server_fd, FIO_NET_CMD_ETA, je, size, &tag, NULL); free(je); return 0; } static int send_update_job_reply(int fd, uint64_t __tag, int error) { uint64_t tag = __tag; uint32_t pdu_error; pdu_error = __cpu_to_le32(error); return fio_net_send_cmd(fd, FIO_NET_CMD_UPDATE_JOB, &pdu_error, sizeof(pdu_error), &tag, NULL); } static int handle_update_job_cmd(struct fio_net_cmd *cmd) { struct cmd_add_job_pdu *pdu = (struct cmd_add_job_pdu *) cmd->payload; struct thread_data *td; uint32_t tnumber; tnumber = le32_to_cpu(pdu->thread_number); dprint(FD_NET, "server: updating options for job %u\n", tnumber); if (!tnumber || tnumber > thread_number) { send_update_job_reply(server_fd, cmd->tag, ENODEV); return 0; } td = &threads[tnumber - 1]; convert_thread_options_to_cpu(&td->o, &pdu->top); send_update_job_reply(server_fd, cmd->tag, 0); return 0; } static int handle_command(struct fio_net_cmd *cmd) { int ret; dprint(FD_NET, "server: got op [%s], pdu=%u, tag=%llx\n", fio_server_op(cmd->opcode), cmd->pdu_len, (unsigned long long) cmd->tag); switch (cmd->opcode) { case FIO_NET_CMD_QUIT: fio_terminate_threads(TERMINATE_ALL); return -1; case FIO_NET_CMD_EXIT: exit_backend = 1; return -1; case FIO_NET_CMD_JOB: ret = handle_job_cmd(cmd); break; case FIO_NET_CMD_JOBLINE: ret = handle_jobline_cmd(cmd); break; case FIO_NET_CMD_PROBE: ret = handle_probe_cmd(cmd); break; case FIO_NET_CMD_SEND_ETA: ret = handle_send_eta_cmd(cmd); break; case FIO_NET_CMD_RUN: ret = handle_run_cmd(cmd); break; case FIO_NET_CMD_UPDATE_JOB: ret = handle_update_job_cmd(cmd); break; default: log_err("fio: unknown opcode: %s\n", fio_server_op(cmd->opcode)); ret = 1; } return ret; } static int handle_connection(int sk) { struct fio_net_cmd *cmd = NULL; int ret = 0; reset_fio_state(); INIT_FLIST_HEAD(&job_list); server_fd = sk; /* read forever */ while (!exit_backend) { struct pollfd pfd = { .fd = sk, .events = POLLIN, }; ret = 0; do { int timeout = 1000; if (!flist_empty(&job_list)) timeout = 100; ret = poll(&pfd, 1, timeout); if (ret < 0) { if (errno == EINTR) break; log_err("fio: poll: %s\n", strerror(errno)); break; } else if (!ret) { fio_server_check_jobs(); continue; } if (pfd.revents & POLLIN) break; if (pfd.revents & (POLLERR|POLLHUP)) { ret = 1; break; } } while (!exit_backend); fio_server_check_jobs(); if (ret < 0) break; cmd = fio_net_recv_cmd(sk); if (!cmd) { ret = -1; break; } ret = handle_command(cmd); if (ret) break; free(cmd); cmd = NULL; } if (cmd) free(cmd); close(sk); _exit(ret); } static int accept_loop(int listen_sk) { struct sockaddr_in addr; socklen_t len = sizeof(addr); struct pollfd pfd; int ret = 0, sk, flags, exitval = 0; dprint(FD_NET, "server enter accept loop\n"); flags = fcntl(listen_sk, F_GETFL); flags |= O_NONBLOCK; fcntl(listen_sk, F_SETFL, flags); while (!exit_backend) { pid_t pid; pfd.fd = listen_sk; pfd.events = POLLIN; do { int timeout = 1000; if (!flist_empty(&conn_list)) timeout = 100; ret = poll(&pfd, 1, timeout); if (ret < 0) { if (errno == EINTR) break; log_err("fio: poll: %s\n", strerror(errno)); break; } else if (!ret) { fio_server_check_conns(); continue; } if (pfd.revents & POLLIN) break; } while (!exit_backend); fio_server_check_conns(); if (exit_backend || ret < 0) break; sk = accept(listen_sk, (struct sockaddr *) &addr, &len); if (sk < 0) { log_err("fio: accept: %s\n", strerror(errno)); return -1; } dprint(FD_NET, "server: connect from %s\n", inet_ntoa(addr.sin_addr)); pid = fork(); if (pid) { close(sk); fio_server_add_conn_pid(pid); continue; } /* exits */ handle_connection(sk); } return exitval; } int fio_server_text_output(int level, const char *buf, size_t len) { struct cmd_text_pdu *pdu; unsigned int tlen; struct timeval tv; if (server_fd == -1) return log_local_buf(buf, len); tlen = sizeof(*pdu) + len; pdu = malloc(tlen); pdu->level = __cpu_to_le32(level); pdu->buf_len = __cpu_to_le32(len); gettimeofday(&tv, NULL); pdu->log_sec = __cpu_to_le64(tv.tv_sec); pdu->log_usec = __cpu_to_le64(tv.tv_usec); memcpy(pdu->buf, buf, len); fio_net_send_cmd(server_fd, FIO_NET_CMD_TEXT, pdu, tlen, NULL, NULL); free(pdu); return len; } static void convert_io_stat(struct io_stat *dst, struct io_stat *src) { dst->max_val = cpu_to_le64(src->max_val); dst->min_val = cpu_to_le64(src->min_val); dst->samples = cpu_to_le64(src->samples); /* * Encode to IEEE 754 for network transfer */ dst->mean.u.i = __cpu_to_le64(fio_double_to_uint64(src->mean.u.f)); dst->S.u.i = __cpu_to_le64(fio_double_to_uint64(src->S.u.f)); } static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src) { int i; for (i = 0; i < DDIR_RWDIR_CNT; i++) { dst->max_run[i] = cpu_to_le64(src->max_run[i]); dst->min_run[i] = cpu_to_le64(src->min_run[i]); dst->max_bw[i] = cpu_to_le64(src->max_bw[i]); dst->min_bw[i] = cpu_to_le64(src->min_bw[i]); dst->io_kb[i] = cpu_to_le64(src->io_kb[i]); dst->agg[i] = cpu_to_le64(src->agg[i]); } dst->kb_base = cpu_to_le32(src->kb_base); dst->unit_base = cpu_to_le32(src->unit_base); dst->groupid = cpu_to_le32(src->groupid); dst->unified_rw_rep = cpu_to_le32(src->unified_rw_rep); } /* * Send a CMD_TS, which packs struct thread_stat and group_run_stats * into a single payload. */ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs) { struct cmd_ts_pdu p; int i, j; dprint(FD_NET, "server sending end stats\n"); memset(&p, 0, sizeof(p)); strcpy(p.ts.name, ts->name); strcpy(p.ts.verror, ts->verror); strcpy(p.ts.description, ts->description); p.ts.error = cpu_to_le32(ts->error); p.ts.thread_number = cpu_to_le32(ts->thread_number); p.ts.groupid = cpu_to_le32(ts->groupid); p.ts.pid = cpu_to_le32(ts->pid); p.ts.members = cpu_to_le32(ts->members); p.ts.unified_rw_rep = cpu_to_le32(ts->unified_rw_rep); for (i = 0; i < DDIR_RWDIR_CNT; i++) { convert_io_stat(&p.ts.clat_stat[i], &ts->clat_stat[i]); convert_io_stat(&p.ts.slat_stat[i], &ts->slat_stat[i]); convert_io_stat(&p.ts.lat_stat[i], &ts->lat_stat[i]); convert_io_stat(&p.ts.bw_stat[i], &ts->bw_stat[i]); } p.ts.usr_time = cpu_to_le64(ts->usr_time); p.ts.sys_time = cpu_to_le64(ts->sys_time); p.ts.ctx = cpu_to_le64(ts->ctx); p.ts.minf = cpu_to_le64(ts->minf); p.ts.majf = cpu_to_le64(ts->majf); p.ts.clat_percentiles = cpu_to_le64(ts->clat_percentiles); for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) { fio_fp64_t *src = &ts->percentile_list[i]; fio_fp64_t *dst = &p.ts.percentile_list[i]; dst->u.i = __cpu_to_le64(fio_double_to_uint64(src->u.f)); } for (i = 0; i < FIO_IO_U_MAP_NR; i++) { p.ts.io_u_map[i] = cpu_to_le32(ts->io_u_map[i]); p.ts.io_u_submit[i] = cpu_to_le32(ts->io_u_submit[i]); p.ts.io_u_complete[i] = cpu_to_le32(ts->io_u_complete[i]); } for (i = 0; i < FIO_IO_U_LAT_U_NR; i++) { p.ts.io_u_lat_u[i] = cpu_to_le32(ts->io_u_lat_u[i]); p.ts.io_u_lat_m[i] = cpu_to_le32(ts->io_u_lat_m[i]); } for (i = 0; i < DDIR_RWDIR_CNT; i++) for (j = 0; j < FIO_IO_U_PLAT_NR; j++) p.ts.io_u_plat[i][j] = cpu_to_le32(ts->io_u_plat[i][j]); for (i = 0; i < DDIR_RWDIR_CNT; i++) { p.ts.total_io_u[i] = cpu_to_le64(ts->total_io_u[i]); p.ts.short_io_u[i] = cpu_to_le64(ts->short_io_u[i]); } p.ts.total_submit = cpu_to_le64(ts->total_submit); p.ts.total_complete = cpu_to_le64(ts->total_complete); for (i = 0; i < DDIR_RWDIR_CNT; i++) { p.ts.io_bytes[i] = cpu_to_le64(ts->io_bytes[i]); p.ts.runtime[i] = cpu_to_le64(ts->runtime[i]); } p.ts.total_run_time = cpu_to_le64(ts->total_run_time); p.ts.continue_on_error = cpu_to_le16(ts->continue_on_error); p.ts.total_err_count = cpu_to_le64(ts->total_err_count); p.ts.first_error = cpu_to_le32(ts->first_error); p.ts.kb_base = cpu_to_le32(ts->kb_base); p.ts.unit_base = cpu_to_le32(ts->unit_base); convert_gs(&p.rs, rs); fio_net_send_cmd(server_fd, FIO_NET_CMD_TS, &p, sizeof(p), NULL, NULL); } void fio_server_send_gs(struct group_run_stats *rs) { struct group_run_stats gs; dprint(FD_NET, "server sending group run stats\n"); convert_gs(&gs, rs); fio_net_send_cmd(server_fd, FIO_NET_CMD_GS, &gs, sizeof(gs), NULL, NULL); } static void convert_agg(struct disk_util_agg *dst, struct disk_util_agg *src) { int i; for (i = 0; i < 2; i++) { dst->ios[i] = cpu_to_le32(src->ios[i]); dst->merges[i] = cpu_to_le32(src->merges[i]); dst->sectors[i] = cpu_to_le64(src->sectors[i]); dst->ticks[i] = cpu_to_le32(src->ticks[i]); } dst->io_ticks = cpu_to_le32(src->io_ticks); dst->time_in_queue = cpu_to_le32(src->time_in_queue); dst->slavecount = cpu_to_le32(src->slavecount); dst->max_util.u.i = __cpu_to_le64(fio_double_to_uint64(src->max_util.u.f)); } static void convert_dus(struct disk_util_stat *dst, struct disk_util_stat *src) { int i; strcpy((char *) dst->name, (char *) src->name); for (i = 0; i < 2; i++) { dst->ios[i] = cpu_to_le32(src->ios[i]); dst->merges[i] = cpu_to_le32(src->merges[i]); dst->sectors[i] = cpu_to_le64(src->sectors[i]); dst->ticks[i] = cpu_to_le32(src->ticks[i]); } dst->io_ticks = cpu_to_le32(src->io_ticks); dst->time_in_queue = cpu_to_le32(src->time_in_queue); dst->msec = cpu_to_le64(src->msec); } void fio_server_send_du(void) { struct disk_util *du; struct flist_head *entry; struct cmd_du_pdu pdu; dprint(FD_NET, "server: sending disk_util %d\n", !flist_empty(&disk_list)); memset(&pdu, 0, sizeof(pdu)); flist_for_each(entry, &disk_list) { du = flist_entry(entry, struct disk_util, list); convert_dus(&pdu.dus, &du->dus); convert_agg(&pdu.agg, &du->agg); fio_net_send_cmd(server_fd, FIO_NET_CMD_DU, &pdu, sizeof(pdu), NULL, NULL); } } /* * Send a command with a separate PDU, not inlined in the command */ static int fio_send_cmd_ext_pdu(int sk, uint16_t opcode, const void *buf, off_t size, uint64_t tag, uint32_t flags) { struct fio_net_cmd cmd; struct iovec iov[2]; iov[0].iov_base = &cmd; iov[0].iov_len = sizeof(cmd); iov[1].iov_base = (void *) buf; iov[1].iov_len = size; __fio_init_net_cmd(&cmd, opcode, size, tag); cmd.flags = __cpu_to_le32(flags); fio_net_cmd_crc_pdu(&cmd, buf); return fio_sendv_data(sk, iov, 2); } static int fio_send_iolog_gz(struct cmd_iolog_pdu *pdu, struct io_log *log) { int ret = 0; #ifdef CONFIG_ZLIB z_stream stream; void *out_pdu; /* * Dirty - since the log is potentially huge, compress it into * FIO_SERVER_MAX_FRAGMENT_PDU chunks and let the receiving * side defragment it. */ out_pdu = malloc(FIO_SERVER_MAX_FRAGMENT_PDU); stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; if (deflateInit(&stream, Z_DEFAULT_COMPRESSION) != Z_OK) { ret = 1; goto err; } stream.next_in = (void *) log->log; stream.avail_in = log->nr_samples * sizeof(struct io_sample); do { unsigned int this_len, flags = 0; int ret; stream.avail_out = FIO_SERVER_MAX_FRAGMENT_PDU; stream.next_out = out_pdu; ret = deflate(&stream, Z_FINISH); /* may be Z_OK, or Z_STREAM_END */ if (ret < 0) goto err_zlib; this_len = FIO_SERVER_MAX_FRAGMENT_PDU - stream.avail_out; if (stream.avail_in) flags = FIO_NET_CMD_F_MORE; ret = fio_send_cmd_ext_pdu(server_fd, FIO_NET_CMD_IOLOG, out_pdu, this_len, 0, flags); if (ret) goto err_zlib; } while (stream.avail_in); err_zlib: deflateEnd(&stream); err: free(out_pdu); #endif return ret; } int fio_send_iolog(struct thread_data *td, struct io_log *log, const char *name) { struct cmd_iolog_pdu pdu; int i, ret = 0; pdu.thread_number = cpu_to_le32(td->thread_number); pdu.nr_samples = __cpu_to_le32(log->nr_samples); pdu.log_type = cpu_to_le32(log->log_type); pdu.compressed = cpu_to_le32(use_zlib); strcpy((char *) pdu.name, name); for (i = 0; i < log->nr_samples; i++) { struct io_sample *s = &log->log[i]; s->time = cpu_to_le64(s->time); s->val = cpu_to_le64(s->val); s->ddir = cpu_to_le32(s->ddir); s->bs = cpu_to_le32(s->bs); } /* * Send header first, it's not compressed. */ ret = fio_send_cmd_ext_pdu(server_fd, FIO_NET_CMD_IOLOG, &pdu, sizeof(pdu), 0, FIO_NET_CMD_F_MORE); if (ret) return ret; /* * Now send actual log, compress if we can, otherwise just plain */ if (use_zlib) return fio_send_iolog_gz(&pdu, log); return fio_send_cmd_ext_pdu(server_fd, FIO_NET_CMD_IOLOG, log->log, log->nr_samples * sizeof(struct io_sample), 0, 0); } void fio_server_send_add_job(struct thread_data *td) { struct cmd_add_job_pdu pdu; memset(&pdu, 0, sizeof(pdu)); pdu.thread_number = cpu_to_le32(td->thread_number); pdu.groupid = cpu_to_le32(td->groupid); convert_thread_options_to_net(&pdu.top, &td->o); fio_net_send_cmd(server_fd, FIO_NET_CMD_ADD_JOB, &pdu, sizeof(pdu), NULL, NULL); } void fio_server_send_start(struct thread_data *td) { assert(server_fd != -1); fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_SERVER_START, 0, NULL); } static int fio_init_server_ip(void) { struct sockaddr *addr; socklen_t socklen; int sk, opt; if (use_ipv6) sk = socket(AF_INET6, SOCK_STREAM, 0); else sk = socket(AF_INET, SOCK_STREAM, 0); if (sk < 0) { log_err("fio: socket: %s\n", strerror(errno)); return -1; } opt = 1; if (setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt)) < 0) { log_err("fio: setsockopt: %s\n", strerror(errno)); close(sk); return -1; } #ifdef SO_REUSEPORT if (setsockopt(sk, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) < 0) { log_err("fio: setsockopt: %s\n", strerror(errno)); close(sk); return -1; } #endif if (use_ipv6) { addr = (struct sockaddr *) &saddr_in6; socklen = sizeof(saddr_in6); saddr_in6.sin6_family = AF_INET6; } else { addr = (struct sockaddr *) &saddr_in; socklen = sizeof(saddr_in); saddr_in.sin_family = AF_INET; } if (bind(sk, addr, socklen) < 0) { log_err("fio: bind: %s\n", strerror(errno)); close(sk); return -1; } return sk; } static int fio_init_server_sock(void) { struct sockaddr_un addr; socklen_t len; mode_t mode; int sk; sk = socket(AF_UNIX, SOCK_STREAM, 0); if (sk < 0) { log_err("fio: socket: %s\n", strerror(errno)); return -1; } mode = umask(000); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, bind_sock); unlink(bind_sock); len = sizeof(addr.sun_family) + strlen(bind_sock) + 1; if (bind(sk, (struct sockaddr *) &addr, len) < 0) { log_err("fio: bind: %s\n", strerror(errno)); close(sk); return -1; } umask(mode); return sk; } static int fio_init_server_connection(void) { char bind_str[128]; int sk; dprint(FD_NET, "starting server\n"); if (!bind_sock) sk = fio_init_server_ip(); else sk = fio_init_server_sock(); if (sk < 0) return sk; if (!bind_sock) { char *p, port[16]; const void *src; int af; if (use_ipv6) { af = AF_INET6; src = &saddr_in6.sin6_addr; } else { af = AF_INET; src = &saddr_in.sin_addr; } p = (char *) inet_ntop(af, src, bind_str, sizeof(bind_str)); sprintf(port, ",%u", fio_net_port); if (p) strcat(p, port); else strcpy(bind_str, port); } else strcpy(bind_str, bind_sock); log_info("fio: server listening on %s\n", bind_str); if (listen(sk, 0) < 0) { log_err("fio: listen: %s\n", strerror(errno)); return -1; } return sk; } int fio_server_parse_host(const char *host, int *ipv6, struct in_addr *inp, struct in6_addr *inp6) { int ret = 0; if (*ipv6) ret = inet_pton(AF_INET6, host, inp6); else ret = inet_pton(AF_INET, host, inp); if (ret != 1) { struct hostent *hent; hent = gethostbyname(host); if (!hent) { log_err("fio: failed to resolve <%s>\n", host); return 0; } if (*ipv6) { if (hent->h_addrtype != AF_INET6) { log_info("fio: falling back to IPv4\n"); *ipv6 = 0; } else memcpy(inp6, hent->h_addr_list[0], 16); } if (!*ipv6) { if (hent->h_addrtype != AF_INET) { log_err("fio: lookup type mismatch\n"); return 0; } memcpy(inp, hent->h_addr_list[0], 4); } ret = 1; } return !(ret == 1); } /* * Parse a host/ip/port string. Reads from 'str'. * * Outputs: * * For IPv4: * *ptr is the host, *port is the port, inp is the destination. * For IPv6: * *ptr is the host, *port is the port, inp6 is the dest, and *ipv6 is 1. * For local domain sockets: * *ptr is the filename, *is_sock is 1. */ int fio_server_parse_string(const char *str, char **ptr, int *is_sock, int *port, struct in_addr *inp, struct in6_addr *inp6, int *ipv6) { const char *host = str; char *portp; int lport = 0; *ptr = NULL; *is_sock = 0; *port = fio_net_port; *ipv6 = 0; if (!strncmp(str, "sock:", 5)) { *ptr = strdup(str + 5); *is_sock = 1; return 0; } /* * Is it ip::port */ if (!strncmp(host, "ip:", 3)) host += 3; else if (!strncmp(host, "ip4:", 4)) host += 4; else if (!strncmp(host, "ip6:", 4)) { host += 4; *ipv6 = 1; } else if (host[0] == ':') { /* String is :port */ host++; lport = atoi(host); if (!lport || lport > 65535) { log_err("fio: bad server port %u\n", lport); return 1; } /* no hostname given, we are done */ *port = lport; return 0; } /* * If no port seen yet, check if there's a last ':' at the end */ if (!lport) { portp = strchr(host, ','); if (portp) { *portp = '\0'; portp++; lport = atoi(portp); if (!lport || lport > 65535) { log_err("fio: bad server port %u\n", lport); return 1; } } } if (lport) *port = lport; if (!strlen(host)) return 0; *ptr = strdup(host); if (fio_server_parse_host(*ptr, ipv6, inp, inp6)) { free(*ptr); *ptr = NULL; return 1; } if (*port == 0) *port = fio_net_port; return 0; } /* * Server arg should be one of: * * sock:/path/to/socket * ip:1.2.3.4 * 1.2.3.4 * * Where sock uses unix domain sockets, and ip binds the server to * a specific interface. If no arguments are given to the server, it * uses IP and binds to 0.0.0.0. * */ static int fio_handle_server_arg(void) { int port = fio_net_port; int is_sock, ret = 0; saddr_in.sin_addr.s_addr = htonl(INADDR_ANY); if (!fio_server_arg) goto out; ret = fio_server_parse_string(fio_server_arg, &bind_sock, &is_sock, &port, &saddr_in.sin_addr, &saddr_in6.sin6_addr, &use_ipv6); if (!is_sock && bind_sock) { free(bind_sock); bind_sock = NULL; } out: fio_net_port = port; saddr_in.sin_port = htons(port); saddr_in6.sin6_port = htons(port); return ret; } static int fio_server(void) { int sk, ret; dprint(FD_NET, "starting server\n"); if (fio_handle_server_arg()) return -1; sk = fio_init_server_connection(); if (sk < 0) return -1; ret = accept_loop(sk); close(sk); if (fio_server_arg) { free(fio_server_arg); fio_server_arg = NULL; } if (bind_sock) free(bind_sock); return ret; } void fio_server_got_signal(int signal) { if (signal == SIGPIPE) server_fd = -1; else { log_info("\nfio: terminating on signal %d\n", signal); exit_backend = 1; } } static int check_existing_pidfile(const char *pidfile) { struct stat sb; char buf[16]; pid_t pid; FILE *f; if (stat(pidfile, &sb)) return 0; f = fopen(pidfile, "r"); if (!f) return 0; if (fread(buf, sb.st_size, 1, f) <= 0) { fclose(f); return 1; } fclose(f); pid = atoi(buf); if (kill(pid, SIGCONT) < 0) return errno != ESRCH; return 1; } static int write_pid(pid_t pid, const char *pidfile) { FILE *fpid; fpid = fopen(pidfile, "w"); if (!fpid) { log_err("fio: failed opening pid file %s\n", pidfile); return 1; } fprintf(fpid, "%u\n", (unsigned int) pid); fclose(fpid); return 0; } /* * If pidfile is specified, background us. */ int fio_start_server(char *pidfile) { pid_t pid; int ret; #if defined(WIN32) WSADATA wsd; WSAStartup(MAKEWORD(2, 2), &wsd); #endif if (!pidfile) return fio_server(); if (check_existing_pidfile(pidfile)) { log_err("fio: pidfile %s exists and server appears alive\n", pidfile); return -1; } pid = fork(); if (pid < 0) { log_err("fio: failed server fork: %s", strerror(errno)); free(pidfile); return -1; } else if (pid) { int ret = write_pid(pid, pidfile); exit(ret); } setsid(); openlog("fio", LOG_NDELAY|LOG_NOWAIT|LOG_PID, LOG_USER); log_syslog = 1; close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); f_out = NULL; f_err = NULL; ret = fio_server(); closelog(); unlink(pidfile); free(pidfile); return ret; } void fio_server_set_arg(const char *arg) { fio_server_arg = strdup(arg); } fio-2.1.3/server.h000066400000000000000000000120131222032232000137320ustar00rootroot00000000000000#ifndef FIO_SERVER_H #define FIO_SERVER_H #include #include #include #include #include "stat.h" #include "os/os.h" #include "diskutil.h" #define FIO_NET_PORT 8765 /* * On-wire encoding is little endian */ struct fio_net_cmd { uint16_t version; /* protocol version */ uint16_t opcode; /* command opcode */ uint32_t flags; /* modifier flags */ uint64_t tag; /* passed back on reply */ uint32_t pdu_len; /* length of post-cmd layload */ /* * These must be immediately before the payload, anything before * these fields are checksummed. */ uint16_t cmd_crc16; /* cmd checksum */ uint16_t pdu_crc16; /* payload checksum */ uint8_t payload[]; /* payload */ }; struct fio_net_cmd_reply { struct flist_head list; struct timeval tv; uint64_t saved_tag; uint16_t opcode; }; enum { FIO_SERVER_VER = 25, FIO_SERVER_MAX_FRAGMENT_PDU = 1024, FIO_NET_CMD_QUIT = 1, FIO_NET_CMD_EXIT = 2, FIO_NET_CMD_JOB = 3, FIO_NET_CMD_JOBLINE = 4, FIO_NET_CMD_TEXT = 5, FIO_NET_CMD_TS = 6, FIO_NET_CMD_GS = 7, FIO_NET_CMD_SEND_ETA = 8, FIO_NET_CMD_ETA = 9, FIO_NET_CMD_PROBE = 10, FIO_NET_CMD_START = 11, FIO_NET_CMD_STOP = 12, FIO_NET_CMD_DU = 13, FIO_NET_CMD_SERVER_START = 14, FIO_NET_CMD_ADD_JOB = 15, FIO_NET_CMD_RUN = 16, FIO_NET_CMD_IOLOG = 17, FIO_NET_CMD_UPDATE_JOB = 18, FIO_NET_CMD_NR = 19, FIO_NET_CMD_F_MORE = 1UL << 0, /* crc does not include the crc fields */ FIO_NET_CMD_CRC_SZ = sizeof(struct fio_net_cmd) - 2 * sizeof(uint16_t), FIO_NET_NAME_MAX = 256, FIO_NET_CLIENT_TIMEOUT = 5000, FIO_PROBE_FLAG_ZLIB = 1UL << 0, }; struct cmd_ts_pdu { struct thread_stat ts; struct group_run_stats rs; }; struct cmd_du_pdu { struct disk_util_stat dus; struct disk_util_agg agg; }; struct cmd_client_probe_pdu { uint64_t flags; }; struct cmd_probe_reply_pdu { uint8_t hostname[64]; uint8_t bigendian; uint8_t fio_version[32]; uint8_t os; uint8_t arch; uint8_t bpp; uint32_t cpus; uint64_t flags; }; struct cmd_single_line_pdu { uint16_t len; uint8_t text[]; }; struct cmd_line_pdu { uint16_t lines; uint16_t client_type; struct cmd_single_line_pdu options[]; }; struct cmd_job_pdu { uint32_t buf_len; uint32_t client_type; uint8_t buf[0]; }; struct cmd_start_pdu { uint32_t jobs; uint32_t stat_outputs; }; struct cmd_end_pdu { uint32_t error; uint32_t signal; }; struct cmd_add_job_pdu { uint32_t thread_number; uint32_t groupid; struct thread_options_pack top; }; struct cmd_text_pdu { uint32_t level; uint32_t buf_len; uint64_t log_sec; uint64_t log_usec; uint8_t buf[0]; }; struct cmd_iolog_pdu { uint32_t thread_number; uint32_t nr_samples; uint32_t log_type; uint32_t compressed; uint8_t name[FIO_NET_NAME_MAX]; struct io_sample samples[0]; }; extern int fio_start_server(char *); extern int fio_server_text_output(int, const char *, size_t); extern int fio_net_send_cmd(int, uint16_t, const void *, off_t, uint64_t *, struct flist_head *); extern int fio_net_send_simple_cmd(int, uint16_t, uint64_t, struct flist_head *); extern void fio_server_set_arg(const char *); extern int fio_server_parse_string(const char *, char **, int *, int *, struct in_addr *, struct in6_addr *, int *); extern int fio_server_parse_host(const char *, int *, struct in_addr *, struct in6_addr *); extern const char *fio_server_op(unsigned int); extern void fio_server_got_signal(int); struct thread_stat; struct group_run_stats; extern void fio_server_send_ts(struct thread_stat *, struct group_run_stats *); extern void fio_server_send_gs(struct group_run_stats *); extern void fio_server_send_du(void); extern void fio_server_idle_loop(void); extern int fio_clients_connect(void); extern int fio_clients_send_ini(const char *); extern void fio_client_add_cmd_option(void *, const char *); extern void fio_client_add_ini_file(void *, const char *); extern int fio_recv_data(int sk, void *p, unsigned int len); extern int fio_send_data(int sk, const void *p, unsigned int len); extern void fio_net_cmd_crc(struct fio_net_cmd *); extern void fio_net_cmd_crc_pdu(struct fio_net_cmd *, const void *); extern struct fio_net_cmd *fio_net_recv_cmd(int sk); extern int fio_send_iolog(struct thread_data *, struct io_log *, const char *); extern void fio_server_send_add_job(struct thread_data *); extern void fio_server_send_start(struct thread_data *); extern int fio_net_send_stop(int sk, int error, int signal); extern int fio_net_send_quit(int sk); extern int exit_backend; extern int fio_net_port; static inline void __fio_init_net_cmd(struct fio_net_cmd *cmd, uint16_t opcode, uint32_t pdu_len, uint64_t tag) { memset(cmd, 0, sizeof(*cmd)); cmd->version = __cpu_to_le16(FIO_SERVER_VER); cmd->opcode = cpu_to_le16(opcode); cmd->tag = cpu_to_le64(tag); cmd->pdu_len = cpu_to_le32(pdu_len); } static inline void fio_init_net_cmd(struct fio_net_cmd *cmd, uint16_t opcode, const void *pdu, uint32_t pdu_len, uint64_t tag) { __fio_init_net_cmd(cmd, opcode, pdu_len, tag); if (pdu) memcpy(&cmd->payload, pdu, pdu_len); } #endif fio-2.1.3/smalloc.c000066400000000000000000000223131222032232000140550ustar00rootroot00000000000000/* * simple memory allocator, backed by mmap() so that it hands out memory * that can be shared across processes and threads */ #include #include #include #include #include #include #include #include #include #include #include "mutex.h" #include "arch/arch.h" #include "os/os.h" #define SMALLOC_REDZONE /* define to detect memory corruption */ #define SMALLOC_BPB 32 /* block size, bytes-per-bit in bitmap */ #define SMALLOC_BPI (sizeof(unsigned int) * 8) #define SMALLOC_BPL (SMALLOC_BPB * SMALLOC_BPI) #define INITIAL_SIZE 8192*1024 /* new pool size */ #define MAX_POOLS 128 /* maximum number of pools to setup */ #define SMALLOC_PRE_RED 0xdeadbeefU #define SMALLOC_POST_RED 0x5aa55aa5U unsigned int smalloc_pool_size = INITIAL_SIZE; const int int_mask = sizeof(int) - 1; struct pool { struct fio_mutex *lock; /* protects this pool */ void *map; /* map of blocks */ unsigned int *bitmap; /* blocks free/busy map */ size_t free_blocks; /* free blocks */ size_t nr_blocks; /* total blocks */ size_t next_non_full; size_t mmap_size; }; struct block_hdr { size_t size; #ifdef SMALLOC_REDZONE unsigned int prered; #endif }; static struct pool mp[MAX_POOLS]; static unsigned int nr_pools; static unsigned int last_pool; static struct fio_rwlock *lock; static inline void pool_lock(struct pool *pool) { fio_mutex_down(pool->lock); } static inline void pool_unlock(struct pool *pool) { fio_mutex_up(pool->lock); } static inline void global_read_lock(void) { fio_rwlock_read(lock); } static inline void global_read_unlock(void) { fio_rwlock_unlock(lock); } static inline void global_write_lock(void) { fio_rwlock_write(lock); } static inline void global_write_unlock(void) { fio_rwlock_unlock(lock); } static inline int ptr_valid(struct pool *pool, void *ptr) { unsigned int pool_size = pool->nr_blocks * SMALLOC_BPL; return (ptr >= pool->map) && (ptr < pool->map + pool_size); } static inline size_t size_to_blocks(size_t size) { return (size + SMALLOC_BPB - 1) / SMALLOC_BPB; } static int blocks_iter(struct pool *pool, unsigned int pool_idx, unsigned int idx, size_t nr_blocks, int (*func)(unsigned int *map, unsigned int mask)) { while (nr_blocks) { unsigned int this_blocks, mask; unsigned int *map; if (pool_idx >= pool->nr_blocks) return 0; map = &pool->bitmap[pool_idx]; this_blocks = nr_blocks; if (this_blocks + idx > SMALLOC_BPI) { this_blocks = SMALLOC_BPI - idx; idx = SMALLOC_BPI - this_blocks; } if (this_blocks == SMALLOC_BPI) mask = -1U; else mask = ((1U << this_blocks) - 1) << idx; if (!func(map, mask)) return 0; nr_blocks -= this_blocks; idx = 0; pool_idx++; } return 1; } static int mask_cmp(unsigned int *map, unsigned int mask) { return !(*map & mask); } static int mask_clear(unsigned int *map, unsigned int mask) { assert((*map & mask) == mask); *map &= ~mask; return 1; } static int mask_set(unsigned int *map, unsigned int mask) { assert(!(*map & mask)); *map |= mask; return 1; } static int blocks_free(struct pool *pool, unsigned int pool_idx, unsigned int idx, size_t nr_blocks) { return blocks_iter(pool, pool_idx, idx, nr_blocks, mask_cmp); } static void set_blocks(struct pool *pool, unsigned int pool_idx, unsigned int idx, size_t nr_blocks) { blocks_iter(pool, pool_idx, idx, nr_blocks, mask_set); } static void clear_blocks(struct pool *pool, unsigned int pool_idx, unsigned int idx, size_t nr_blocks) { blocks_iter(pool, pool_idx, idx, nr_blocks, mask_clear); } static int find_next_zero(int word, int start) { assert(word != -1U); word >>= start; return ffz(word) + start; } static int add_pool(struct pool *pool, unsigned int alloc_size) { int bitmap_blocks; void *ptr; #ifdef SMALLOC_REDZONE alloc_size += sizeof(unsigned int); #endif alloc_size += sizeof(struct block_hdr); if (alloc_size < INITIAL_SIZE) alloc_size = INITIAL_SIZE; /* round up to nearest full number of blocks */ alloc_size = (alloc_size + SMALLOC_BPL - 1) & ~(SMALLOC_BPL - 1); bitmap_blocks = alloc_size / SMALLOC_BPL; alloc_size += bitmap_blocks * sizeof(unsigned int); pool->mmap_size = alloc_size; pool->nr_blocks = bitmap_blocks; pool->free_blocks = bitmap_blocks * SMALLOC_BPB; ptr = mmap(NULL, alloc_size, PROT_READ|PROT_WRITE, MAP_SHARED | OS_MAP_ANON, -1, 0); if (ptr == MAP_FAILED) goto out_fail; memset(ptr, 0, alloc_size); pool->map = ptr; pool->bitmap = (void *) ptr + (pool->nr_blocks * SMALLOC_BPL); pool->lock = fio_mutex_init(FIO_MUTEX_UNLOCKED); if (!pool->lock) goto out_fail; nr_pools++; return 0; out_fail: fprintf(stderr, "smalloc: failed adding pool\n"); if (pool->map) munmap(pool->map, pool->mmap_size); return 1; } void sinit(void) { int ret; lock = fio_rwlock_init(); ret = add_pool(&mp[0], INITIAL_SIZE); assert(!ret); } static void cleanup_pool(struct pool *pool) { /* * This will also remove the temporary file we used as a backing * store, it was already unlinked */ munmap(pool->map, pool->mmap_size); if (pool->lock) fio_mutex_remove(pool->lock); } void scleanup(void) { unsigned int i; for (i = 0; i < nr_pools; i++) cleanup_pool(&mp[i]); if (lock) fio_rwlock_remove(lock); } #ifdef SMALLOC_REDZONE static void *postred_ptr(struct block_hdr *hdr) { uintptr_t ptr; ptr = (uintptr_t) hdr + hdr->size - sizeof(unsigned int); ptr = (ptr + int_mask) & ~int_mask; return (void *) ptr; } static void fill_redzone(struct block_hdr *hdr) { unsigned int *postred = postred_ptr(hdr); hdr->prered = SMALLOC_PRE_RED; *postred = SMALLOC_POST_RED; } static void sfree_check_redzone(struct block_hdr *hdr) { unsigned int *postred = postred_ptr(hdr); if (hdr->prered != SMALLOC_PRE_RED) { fprintf(stderr, "smalloc pre redzone destroyed!\n"); fprintf(stderr, " ptr=%p, prered=%x, expected %x\n", hdr, hdr->prered, SMALLOC_PRE_RED); assert(0); } if (*postred != SMALLOC_POST_RED) { fprintf(stderr, "smalloc post redzone destroyed!\n"); fprintf(stderr, " ptr=%p, postred=%x, expected %x\n", hdr, *postred, SMALLOC_POST_RED); assert(0); } } #else static void fill_redzone(struct block_hdr *hdr) { } static void sfree_check_redzone(struct block_hdr *hdr) { } #endif static void sfree_pool(struct pool *pool, void *ptr) { struct block_hdr *hdr; unsigned int i, idx; unsigned long offset; if (!ptr) return; ptr -= sizeof(*hdr); hdr = ptr; assert(ptr_valid(pool, ptr)); sfree_check_redzone(hdr); offset = ptr - pool->map; i = offset / SMALLOC_BPL; idx = (offset % SMALLOC_BPL) / SMALLOC_BPB; pool_lock(pool); clear_blocks(pool, i, idx, size_to_blocks(hdr->size)); if (i < pool->next_non_full) pool->next_non_full = i; pool->free_blocks += size_to_blocks(hdr->size); pool_unlock(pool); } void sfree(void *ptr) { struct pool *pool = NULL; unsigned int i; if (!ptr) return; global_read_lock(); for (i = 0; i < nr_pools; i++) { if (ptr_valid(&mp[i], ptr)) { pool = &mp[i]; break; } } global_read_unlock(); assert(pool); sfree_pool(pool, ptr); } static void *__smalloc_pool(struct pool *pool, size_t size) { size_t nr_blocks; unsigned int i; unsigned int offset; unsigned int last_idx; void *ret = NULL; pool_lock(pool); nr_blocks = size_to_blocks(size); if (nr_blocks > pool->free_blocks) goto fail; i = pool->next_non_full; last_idx = 0; offset = -1U; while (i < pool->nr_blocks) { unsigned int idx; if (pool->bitmap[i] == -1U) { i++; pool->next_non_full = i; last_idx = 0; continue; } idx = find_next_zero(pool->bitmap[i], last_idx); if (!blocks_free(pool, i, idx, nr_blocks)) { idx += nr_blocks; if (idx < SMALLOC_BPI) last_idx = idx; else { last_idx = 0; while (idx >= SMALLOC_BPI) { i++; idx -= SMALLOC_BPI; } } continue; } set_blocks(pool, i, idx, nr_blocks); offset = i * SMALLOC_BPL + idx * SMALLOC_BPB; break; } if (i < pool->nr_blocks) { pool->free_blocks -= nr_blocks; ret = pool->map + offset; } fail: pool_unlock(pool); return ret; } static void *smalloc_pool(struct pool *pool, size_t size) { size_t alloc_size = size + sizeof(struct block_hdr); void *ptr; /* * Round to int alignment, so that the postred pointer will * be naturally aligned as well. */ #ifdef SMALLOC_REDZONE alloc_size += sizeof(unsigned int); alloc_size = (alloc_size + int_mask) & ~int_mask; #endif ptr = __smalloc_pool(pool, alloc_size); if (ptr) { struct block_hdr *hdr = ptr; hdr->size = alloc_size; fill_redzone(hdr); ptr += sizeof(*hdr); memset(ptr, 0, size); } return ptr; } void *smalloc(size_t size) { unsigned int i; if (size != (unsigned int) size) return NULL; global_write_lock(); i = last_pool; do { for (; i < nr_pools; i++) { void *ptr = smalloc_pool(&mp[i], size); if (ptr) { last_pool = i; global_write_unlock(); return ptr; } } if (last_pool) { last_pool = 0; continue; } if (nr_pools + 1 > MAX_POOLS) break; else { i = nr_pools; if (add_pool(&mp[nr_pools], size)) goto out; } } while (1); out: global_write_unlock(); return NULL; } char *smalloc_strdup(const char *str) { char *ptr; ptr = smalloc(strlen(str) + 1); strcpy(ptr, str); return ptr; } fio-2.1.3/smalloc.h000066400000000000000000000003661222032232000140660ustar00rootroot00000000000000#ifndef FIO_SMALLOC_H #define FIO_SMALLOC_H extern void *smalloc(size_t); extern void sfree(void *); extern char *smalloc_strdup(const char *); extern void sinit(void); extern void scleanup(void); extern unsigned int smalloc_pool_size; #endif fio-2.1.3/stat.c000066400000000000000000001277351222032232000134140ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "fio.h" #include "diskutil.h" #include "lib/ieee754.h" #include "json.h" #include "lib/getrusage.h" #include "idletime.h" static struct fio_mutex *stat_mutex; void update_rusage_stat(struct thread_data *td) { struct thread_stat *ts = &td->ts; fio_getrusage(&td->ru_end); ts->usr_time += mtime_since(&td->ru_start.ru_utime, &td->ru_end.ru_utime); ts->sys_time += mtime_since(&td->ru_start.ru_stime, &td->ru_end.ru_stime); ts->ctx += td->ru_end.ru_nvcsw + td->ru_end.ru_nivcsw - (td->ru_start.ru_nvcsw + td->ru_start.ru_nivcsw); ts->minf += td->ru_end.ru_minflt - td->ru_start.ru_minflt; ts->majf += td->ru_end.ru_majflt - td->ru_start.ru_majflt; memcpy(&td->ru_start, &td->ru_end, sizeof(td->ru_end)); } /* * Given a latency, return the index of the corresponding bucket in * the structure tracking percentiles. * * (1) find the group (and error bits) that the value (latency) * belongs to by looking at its MSB. (2) find the bucket number in the * group by looking at the index bits. * */ static unsigned int plat_val_to_idx(unsigned int val) { unsigned int msb, error_bits, base, offset, idx; /* Find MSB starting from bit 0 */ if (val == 0) msb = 0; else msb = (sizeof(val)*8) - __builtin_clz(val) - 1; /* * MSB <= (FIO_IO_U_PLAT_BITS-1), cannot be rounded off. Use * all bits of the sample as index */ if (msb <= FIO_IO_U_PLAT_BITS) return val; /* Compute the number of error bits to discard*/ error_bits = msb - FIO_IO_U_PLAT_BITS; /* Compute the number of buckets before the group */ base = (error_bits + 1) << FIO_IO_U_PLAT_BITS; /* * Discard the error bits and apply the mask to find the * index for the buckets in the group */ offset = (FIO_IO_U_PLAT_VAL - 1) & (val >> error_bits); /* Make sure the index does not exceed (array size - 1) */ idx = (base + offset) < (FIO_IO_U_PLAT_NR - 1) ? (base + offset) : (FIO_IO_U_PLAT_NR - 1); return idx; } /* * Convert the given index of the bucket array to the value * represented by the bucket */ static unsigned int plat_idx_to_val(unsigned int idx) { unsigned int error_bits, k, base; assert(idx < FIO_IO_U_PLAT_NR); /* MSB <= (FIO_IO_U_PLAT_BITS-1), cannot be rounded off. Use * all bits of the sample as index */ if (idx < (FIO_IO_U_PLAT_VAL << 1)) return idx; /* Find the group and compute the minimum value of that group */ error_bits = (idx >> FIO_IO_U_PLAT_BITS) - 1; base = 1 << (error_bits + FIO_IO_U_PLAT_BITS); /* Find its bucket number of the group */ k = idx % FIO_IO_U_PLAT_VAL; /* Return the mean of the range of the bucket */ return base + ((k + 0.5) * (1 << error_bits)); } static int double_cmp(const void *a, const void *b) { const fio_fp64_t fa = *(const fio_fp64_t *) a; const fio_fp64_t fb = *(const fio_fp64_t *) b; int cmp = 0; if (fa.u.f > fb.u.f) cmp = 1; else if (fa.u.f < fb.u.f) cmp = -1; return cmp; } unsigned int calc_clat_percentiles(unsigned int *io_u_plat, unsigned long nr, fio_fp64_t *plist, unsigned int **output, unsigned int *maxv, unsigned int *minv) { unsigned long sum = 0; unsigned int len, i, j = 0; unsigned int oval_len = 0; unsigned int *ovals = NULL; int is_last; *minv = -1U; *maxv = 0; len = 0; while (len < FIO_IO_U_LIST_MAX_LEN && plist[len].u.f != 0.0) len++; if (!len) return 0; /* * Sort the percentile list. Note that it may already be sorted if * we are using the default values, but since it's a short list this * isn't a worry. Also note that this does not work for NaN values. */ if (len > 1) qsort((void *)plist, len, sizeof(plist[0]), double_cmp); /* * Calculate bucket values, note down max and min values */ is_last = 0; for (i = 0; i < FIO_IO_U_PLAT_NR && !is_last; i++) { sum += io_u_plat[i]; while (sum >= (plist[j].u.f / 100.0 * nr)) { assert(plist[j].u.f <= 100.0); if (j == oval_len) { oval_len += 100; ovals = realloc(ovals, oval_len * sizeof(unsigned int)); } ovals[j] = plat_idx_to_val(i); if (ovals[j] < *minv) *minv = ovals[j]; if (ovals[j] > *maxv) *maxv = ovals[j]; is_last = (j == len - 1); if (is_last) break; j++; } } *output = ovals; return len; } /* * Find and display the p-th percentile of clat */ static void show_clat_percentiles(unsigned int *io_u_plat, unsigned long nr, fio_fp64_t *plist, unsigned int precision) { unsigned int len, j = 0, minv, maxv; unsigned int *ovals; int is_last, per_line, scale_down; char fmt[32]; len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv); if (!len) goto out; /* * We default to usecs, but if the value range is such that we * should scale down to msecs, do that. */ if (minv > 2000 && maxv > 99999) { scale_down = 1; log_info(" clat percentiles (msec):\n |"); } else { scale_down = 0; log_info(" clat percentiles (usec):\n |"); } snprintf(fmt, sizeof(fmt), "%%1.%uf", precision); per_line = (80 - 7) / (precision + 14); for (j = 0; j < len; j++) { char fbuf[16], *ptr = fbuf; /* for formatting */ if (j != 0 && (j % per_line) == 0) log_info(" |"); /* end of the list */ is_last = (j == len - 1); if (plist[j].u.f < 10.0) ptr += sprintf(fbuf, " "); snprintf(ptr, sizeof(fbuf), fmt, plist[j].u.f); if (scale_down) ovals[j] = (ovals[j] + 999) / 1000; log_info(" %sth=[%5u]%c", fbuf, ovals[j], is_last ? '\n' : ','); if (is_last) break; if ((j % per_line) == per_line - 1) /* for formatting */ log_info("\n"); } out: if (ovals) free(ovals); } int calc_lat(struct io_stat *is, unsigned long *min, unsigned long *max, double *mean, double *dev) { double n = (double) is->samples; if (n == 0) return 0; *min = is->min_val; *max = is->max_val; *mean = is->mean.u.f; if (n > 1.0) *dev = sqrt(is->S.u.f / (n - 1.0)); else *dev = 0; return 1; } void show_group_stats(struct group_run_stats *rs) { char *p1, *p2, *p3, *p4; const char *ddir_str[] = { " READ", " WRITE" , " TRIM"}; int i; log_info("\nRun status group %d (all jobs):\n", rs->groupid); for (i = 0; i < DDIR_RWDIR_CNT; i++) { const int i2p = is_power_of_2(rs->kb_base); if (!rs->max_run[i]) continue; p1 = num2str(rs->io_kb[i], 6, rs->kb_base, i2p, 8); p2 = num2str(rs->agg[i], 6, rs->kb_base, i2p, rs->unit_base); p3 = num2str(rs->min_bw[i], 6, rs->kb_base, i2p, rs->unit_base); p4 = num2str(rs->max_bw[i], 6, rs->kb_base, i2p, rs->unit_base); log_info("%s: io=%s, aggrb=%s/s, minb=%s/s, maxb=%s/s," " mint=%llumsec, maxt=%llumsec\n", rs->unified_rw_rep ? " MIXED" : ddir_str[i], p1, p2, p3, p4, (unsigned long long) rs->min_run[i], (unsigned long long) rs->max_run[i]); free(p1); free(p2); free(p3); free(p4); } } void stat_calc_dist(unsigned int *map, unsigned long total, double *io_u_dist) { int i; /* * Do depth distribution calculations */ for (i = 0; i < FIO_IO_U_MAP_NR; i++) { if (total) { io_u_dist[i] = (double) map[i] / (double) total; io_u_dist[i] *= 100.0; if (io_u_dist[i] < 0.1 && map[i]) io_u_dist[i] = 0.1; } else io_u_dist[i] = 0.0; } } static void stat_calc_lat(struct thread_stat *ts, double *dst, unsigned int *src, int nr) { unsigned long total = ddir_rw_sum(ts->total_io_u); int i; /* * Do latency distribution calculations */ for (i = 0; i < nr; i++) { if (total) { dst[i] = (double) src[i] / (double) total; dst[i] *= 100.0; if (dst[i] < 0.01 && src[i]) dst[i] = 0.01; } else dst[i] = 0.0; } } void stat_calc_lat_u(struct thread_stat *ts, double *io_u_lat) { stat_calc_lat(ts, io_u_lat, ts->io_u_lat_u, FIO_IO_U_LAT_U_NR); } void stat_calc_lat_m(struct thread_stat *ts, double *io_u_lat) { stat_calc_lat(ts, io_u_lat, ts->io_u_lat_m, FIO_IO_U_LAT_M_NR); } static void display_lat(const char *name, unsigned long min, unsigned long max, double mean, double dev) { const char *base = "(usec)"; char *minp, *maxp; if (!usec_to_msec(&min, &max, &mean, &dev)) base = "(msec)"; minp = num2str(min, 6, 1, 0, 0); maxp = num2str(max, 6, 1, 0, 0); log_info(" %s %s: min=%s, max=%s, avg=%5.02f," " stdev=%5.02f\n", name, base, minp, maxp, mean, dev); free(minp); free(maxp); } static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, int ddir) { const char *ddir_str[] = { "read ", "write", "trim" }; unsigned long min, max, runt; unsigned long long bw, iops; double mean, dev; char *io_p, *bw_p, *iops_p; int i2p; assert(ddir_rw(ddir)); if (!ts->runtime[ddir]) return; i2p = is_power_of_2(rs->kb_base); runt = ts->runtime[ddir]; bw = (1000 * ts->io_bytes[ddir]) / runt; io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p, 8); bw_p = num2str(bw, 6, 1, i2p, ts->unit_base); iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt; iops_p = num2str(iops, 6, 1, 0, 0); log_info(" %s: io=%s, bw=%s/s, iops=%s, runt=%6llumsec\n", rs->unified_rw_rep ? "mixed" : ddir_str[ddir], io_p, bw_p, iops_p, (unsigned long long) ts->runtime[ddir]); free(io_p); free(bw_p); free(iops_p); if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev)) display_lat("slat", min, max, mean, dev); if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev)) display_lat("clat", min, max, mean, dev); if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev)) display_lat(" lat", min, max, mean, dev); if (ts->clat_percentiles) { show_clat_percentiles(ts->io_u_plat[ddir], ts->clat_stat[ddir].samples, ts->percentile_list, ts->percentile_precision); } if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) { double p_of_agg = 100.0, fkb_base = (double)rs->kb_base; const char *bw_str = (rs->unit_base == 1 ? "Kbit" : "KB"); if (rs->unit_base == 1) { min *= 8.0; max *= 8.0; mean *= 8.0; dev *= 8.0; } if (rs->agg[ddir]) { p_of_agg = mean * 100 / (double) rs->agg[ddir]; if (p_of_agg > 100.0) p_of_agg = 100.0; } if (mean > fkb_base * fkb_base) { min /= fkb_base; max /= fkb_base; mean /= fkb_base; dev /= fkb_base; bw_str = (rs->unit_base == 1 ? "Mbit" : "MB"); } log_info(" bw (%-4s/s): min=%5lu, max=%5lu, per=%3.2f%%," " avg=%5.02f, stdev=%5.02f\n", bw_str, min, max, p_of_agg, mean, dev); } } static int show_lat(double *io_u_lat, int nr, const char **ranges, const char *msg) { int new_line = 1, i, line = 0, shown = 0; for (i = 0; i < nr; i++) { if (io_u_lat[i] <= 0.0) continue; shown = 1; if (new_line) { if (line) log_info("\n"); log_info(" lat (%s) : ", msg); new_line = 0; line = 0; } if (line) log_info(", "); log_info("%s%3.2f%%", ranges[i], io_u_lat[i]); line++; if (line == 5) new_line = 1; } if (shown) log_info("\n"); return shown; } static void show_lat_u(double *io_u_lat_u) { const char *ranges[] = { "2=", "4=", "10=", "20=", "50=", "100=", "250=", "500=", "750=", "1000=", }; show_lat(io_u_lat_u, FIO_IO_U_LAT_U_NR, ranges, "usec"); } static void show_lat_m(double *io_u_lat_m) { const char *ranges[] = { "2=", "4=", "10=", "20=", "50=", "100=", "250=", "500=", "750=", "1000=", "2000=", ">=2000=", }; show_lat(io_u_lat_m, FIO_IO_U_LAT_M_NR, ranges, "msec"); } static void show_latencies(struct thread_stat *ts) { double io_u_lat_u[FIO_IO_U_LAT_U_NR]; double io_u_lat_m[FIO_IO_U_LAT_M_NR]; stat_calc_lat_u(ts, io_u_lat_u); stat_calc_lat_m(ts, io_u_lat_m); show_lat_u(io_u_lat_u); show_lat_m(io_u_lat_m); } void show_thread_status(struct thread_stat *ts, struct group_run_stats *rs) { double usr_cpu, sys_cpu; unsigned long runtime; double io_u_dist[FIO_IO_U_MAP_NR]; time_t time_p; char time_buf[64]; if (!(ts->io_bytes[DDIR_READ] + ts->io_bytes[DDIR_WRITE] + ts->io_bytes[DDIR_TRIM]) && !(ts->total_io_u[DDIR_READ] + ts->total_io_u[DDIR_WRITE] + ts->total_io_u[DDIR_TRIM])) return; time(&time_p); os_ctime_r((const time_t *) &time_p, time_buf, sizeof(time_buf)); if (!ts->error) { log_info("%s: (groupid=%d, jobs=%d): err=%2d: pid=%d: %s", ts->name, ts->groupid, ts->members, ts->error, (int) ts->pid, time_buf); } else { log_info("%s: (groupid=%d, jobs=%d): err=%2d (%s): pid=%d: %s", ts->name, ts->groupid, ts->members, ts->error, ts->verror, (int) ts->pid, time_buf); } if (strlen(ts->description)) log_info(" Description : [%s]\n", ts->description); if (ts->io_bytes[DDIR_READ]) show_ddir_status(rs, ts, DDIR_READ); if (ts->io_bytes[DDIR_WRITE]) show_ddir_status(rs, ts, DDIR_WRITE); if (ts->io_bytes[DDIR_TRIM]) show_ddir_status(rs, ts, DDIR_TRIM); show_latencies(ts); runtime = ts->total_run_time; if (runtime) { double runt = (double) runtime; usr_cpu = (double) ts->usr_time * 100 / runt; sys_cpu = (double) ts->sys_time * 100 / runt; } else { usr_cpu = 0; sys_cpu = 0; } log_info(" cpu : usr=%3.2f%%, sys=%3.2f%%, ctx=%llu," " majf=%llu, minf=%llu\n", usr_cpu, sys_cpu, (unsigned long long) ts->ctx, (unsigned long long) ts->majf, (unsigned long long) ts->minf); stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist); log_info(" IO depths : 1=%3.1f%%, 2=%3.1f%%, 4=%3.1f%%, 8=%3.1f%%," " 16=%3.1f%%, 32=%3.1f%%, >=64=%3.1f%%\n", io_u_dist[0], io_u_dist[1], io_u_dist[2], io_u_dist[3], io_u_dist[4], io_u_dist[5], io_u_dist[6]); stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist); log_info(" submit : 0=%3.1f%%, 4=%3.1f%%, 8=%3.1f%%, 16=%3.1f%%," " 32=%3.1f%%, 64=%3.1f%%, >=64=%3.1f%%\n", io_u_dist[0], io_u_dist[1], io_u_dist[2], io_u_dist[3], io_u_dist[4], io_u_dist[5], io_u_dist[6]); stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist); log_info(" complete : 0=%3.1f%%, 4=%3.1f%%, 8=%3.1f%%, 16=%3.1f%%," " 32=%3.1f%%, 64=%3.1f%%, >=64=%3.1f%%\n", io_u_dist[0], io_u_dist[1], io_u_dist[2], io_u_dist[3], io_u_dist[4], io_u_dist[5], io_u_dist[6]); log_info(" issued : total=r=%llu/w=%llu/d=%llu," " short=r=%llu/w=%llu/d=%llu\n", (unsigned long long) ts->total_io_u[0], (unsigned long long) ts->total_io_u[1], (unsigned long long) ts->total_io_u[2], (unsigned long long) ts->short_io_u[0], (unsigned long long) ts->short_io_u[1], (unsigned long long) ts->short_io_u[2]); if (ts->continue_on_error) { log_info(" errors : total=%llu, first_error=%d/<%s>\n", (unsigned long long)ts->total_err_count, ts->first_error, strerror(ts->first_error)); } } static void show_ddir_status_terse(struct thread_stat *ts, struct group_run_stats *rs, int ddir) { unsigned long min, max; unsigned long long bw, iops; unsigned int *ovals = NULL; double mean, dev; unsigned int len, minv, maxv; int i; assert(ddir_rw(ddir)); iops = bw = 0; if (ts->runtime[ddir]) { uint64_t runt = ts->runtime[ddir]; bw = ((1000 * ts->io_bytes[ddir]) / runt) / 1024; iops = (1000 * (uint64_t) ts->total_io_u[ddir]) / runt; } log_info(";%llu;%llu;%llu;%llu", (unsigned long long) ts->io_bytes[ddir] >> 10, bw, iops, (unsigned long long) ts->runtime[ddir]); if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev)) log_info(";%lu;%lu;%f;%f", min, max, mean, dev); else log_info(";%lu;%lu;%f;%f", 0UL, 0UL, 0.0, 0.0); if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev)) log_info(";%lu;%lu;%f;%f", min, max, mean, dev); else log_info(";%lu;%lu;%f;%f", 0UL, 0UL, 0.0, 0.0); if (ts->clat_percentiles) { len = calc_clat_percentiles(ts->io_u_plat[ddir], ts->clat_stat[ddir].samples, ts->percentile_list, &ovals, &maxv, &minv); } else len = 0; for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) { if (i >= len) { log_info(";0%%=0"); continue; } log_info(";%f%%=%u", ts->percentile_list[i].u.f, ovals[i]); } if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev)) log_info(";%lu;%lu;%f;%f", min, max, mean, dev); else log_info(";%lu;%lu;%f;%f", 0UL, 0UL, 0.0, 0.0); if (ovals) free(ovals); if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) { double p_of_agg = 100.0; if (rs->agg[ddir]) { p_of_agg = mean * 100 / (double) rs->agg[ddir]; if (p_of_agg > 100.0) p_of_agg = 100.0; } log_info(";%lu;%lu;%f%%;%f;%f", min, max, p_of_agg, mean, dev); } else log_info(";%lu;%lu;%f%%;%f;%f", 0UL, 0UL, 0.0, 0.0, 0.0); } static void add_ddir_status_json(struct thread_stat *ts, struct group_run_stats *rs, int ddir, struct json_object *parent) { unsigned long min, max; unsigned long long bw, iops; unsigned int *ovals = NULL; double mean, dev; unsigned int len, minv, maxv; int i; const char *ddirname[] = {"read", "write", "trim"}; struct json_object *dir_object, *tmp_object, *percentile_object; char buf[120]; double p_of_agg = 100.0; assert(ddir_rw(ddir)); if (ts->unified_rw_rep && ddir != DDIR_READ) return; dir_object = json_create_object(); json_object_add_value_object(parent, ts->unified_rw_rep ? "mixed" : ddirname[ddir], dir_object); iops = bw = 0; if (ts->runtime[ddir]) { uint64_t runt = ts->runtime[ddir]; bw = ((1000 * ts->io_bytes[ddir]) / runt) / 1024; iops = (1000 * (uint64_t) ts->total_io_u[ddir]) / runt; } json_object_add_value_int(dir_object, "io_bytes", ts->io_bytes[ddir] >> 10); json_object_add_value_int(dir_object, "bw", bw); json_object_add_value_int(dir_object, "iops", iops); json_object_add_value_int(dir_object, "runtime", ts->runtime[ddir]); if (!calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev)) { min = max = 0; mean = dev = 0.0; } tmp_object = json_create_object(); json_object_add_value_object(dir_object, "slat", tmp_object); json_object_add_value_int(tmp_object, "min", min); json_object_add_value_int(tmp_object, "max", max); json_object_add_value_float(tmp_object, "mean", mean); json_object_add_value_float(tmp_object, "stddev", dev); if (!calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev)) { min = max = 0; mean = dev = 0.0; } tmp_object = json_create_object(); json_object_add_value_object(dir_object, "clat", tmp_object); json_object_add_value_int(tmp_object, "min", min); json_object_add_value_int(tmp_object, "max", max); json_object_add_value_float(tmp_object, "mean", mean); json_object_add_value_float(tmp_object, "stddev", dev); if (ts->clat_percentiles) { len = calc_clat_percentiles(ts->io_u_plat[ddir], ts->clat_stat[ddir].samples, ts->percentile_list, &ovals, &maxv, &minv); } else len = 0; percentile_object = json_create_object(); json_object_add_value_object(tmp_object, "percentile", percentile_object); for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) { if (i >= len) { json_object_add_value_int(percentile_object, "0.00", 0); continue; } snprintf(buf, sizeof(buf), "%f", ts->percentile_list[i].u.f); json_object_add_value_int(percentile_object, (const char *)buf, ovals[i]); } if (!calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev)) { min = max = 0; mean = dev = 0.0; } tmp_object = json_create_object(); json_object_add_value_object(dir_object, "lat", tmp_object); json_object_add_value_int(tmp_object, "min", min); json_object_add_value_int(tmp_object, "max", max); json_object_add_value_float(tmp_object, "mean", mean); json_object_add_value_float(tmp_object, "stddev", dev); if (ovals) free(ovals); if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) { if (rs->agg[ddir]) { p_of_agg = mean * 100 / (double) rs->agg[ddir]; if (p_of_agg > 100.0) p_of_agg = 100.0; } } else { min = max = 0; p_of_agg = mean = dev = 0.0; } json_object_add_value_int(dir_object, "bw_min", min); json_object_add_value_int(dir_object, "bw_max", max); json_object_add_value_float(dir_object, "bw_agg", mean); json_object_add_value_float(dir_object, "bw_mean", mean); json_object_add_value_float(dir_object, "bw_dev", dev); } static void show_thread_status_terse_v2(struct thread_stat *ts, struct group_run_stats *rs) { double io_u_dist[FIO_IO_U_MAP_NR]; double io_u_lat_u[FIO_IO_U_LAT_U_NR]; double io_u_lat_m[FIO_IO_U_LAT_M_NR]; double usr_cpu, sys_cpu; int i; /* General Info */ log_info("2;%s;%d;%d", ts->name, ts->groupid, ts->error); /* Log Read Status */ show_ddir_status_terse(ts, rs, DDIR_READ); /* Log Write Status */ show_ddir_status_terse(ts, rs, DDIR_WRITE); /* Log Trim Status */ show_ddir_status_terse(ts, rs, DDIR_TRIM); /* CPU Usage */ if (ts->total_run_time) { double runt = (double) ts->total_run_time; usr_cpu = (double) ts->usr_time * 100 / runt; sys_cpu = (double) ts->sys_time * 100 / runt; } else { usr_cpu = 0; sys_cpu = 0; } log_info(";%f%%;%f%%;%llu;%llu;%llu", usr_cpu, sys_cpu, (unsigned long long) ts->ctx, (unsigned long long) ts->majf, (unsigned long long) ts->minf); /* Calc % distribution of IO depths, usecond, msecond latency */ stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist); stat_calc_lat_u(ts, io_u_lat_u); stat_calc_lat_m(ts, io_u_lat_m); /* Only show fixed 7 I/O depth levels*/ log_info(";%3.1f%%;%3.1f%%;%3.1f%%;%3.1f%%;%3.1f%%;%3.1f%%;%3.1f%%", io_u_dist[0], io_u_dist[1], io_u_dist[2], io_u_dist[3], io_u_dist[4], io_u_dist[5], io_u_dist[6]); /* Microsecond latency */ for (i = 0; i < FIO_IO_U_LAT_U_NR; i++) log_info(";%3.2f%%", io_u_lat_u[i]); /* Millisecond latency */ for (i = 0; i < FIO_IO_U_LAT_M_NR; i++) log_info(";%3.2f%%", io_u_lat_m[i]); /* Additional output if continue_on_error set - default off*/ if (ts->continue_on_error) log_info(";%llu;%d", (unsigned long long) ts->total_err_count, ts->first_error); log_info("\n"); /* Additional output if description is set */ if (ts->description) log_info(";%s", ts->description); log_info("\n"); } static void show_thread_status_terse_v3_v4(struct thread_stat *ts, struct group_run_stats *rs, int ver) { double io_u_dist[FIO_IO_U_MAP_NR]; double io_u_lat_u[FIO_IO_U_LAT_U_NR]; double io_u_lat_m[FIO_IO_U_LAT_M_NR]; double usr_cpu, sys_cpu; int i; /* General Info */ log_info("%d;%s;%s;%d;%d", ver, fio_version_string, ts->name, ts->groupid, ts->error); /* Log Read Status */ show_ddir_status_terse(ts, rs, DDIR_READ); /* Log Write Status */ show_ddir_status_terse(ts, rs, DDIR_WRITE); /* Log Trim Status */ if (ver == 4) show_ddir_status_terse(ts, rs, DDIR_TRIM); /* CPU Usage */ if (ts->total_run_time) { double runt = (double) ts->total_run_time; usr_cpu = (double) ts->usr_time * 100 / runt; sys_cpu = (double) ts->sys_time * 100 / runt; } else { usr_cpu = 0; sys_cpu = 0; } log_info(";%f%%;%f%%;%llu;%llu;%llu", usr_cpu, sys_cpu, (unsigned long long) ts->ctx, (unsigned long long) ts->majf, (unsigned long long) ts->minf); /* Calc % distribution of IO depths, usecond, msecond latency */ stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist); stat_calc_lat_u(ts, io_u_lat_u); stat_calc_lat_m(ts, io_u_lat_m); /* Only show fixed 7 I/O depth levels*/ log_info(";%3.1f%%;%3.1f%%;%3.1f%%;%3.1f%%;%3.1f%%;%3.1f%%;%3.1f%%", io_u_dist[0], io_u_dist[1], io_u_dist[2], io_u_dist[3], io_u_dist[4], io_u_dist[5], io_u_dist[6]); /* Microsecond latency */ for (i = 0; i < FIO_IO_U_LAT_U_NR; i++) log_info(";%3.2f%%", io_u_lat_u[i]); /* Millisecond latency */ for (i = 0; i < FIO_IO_U_LAT_M_NR; i++) log_info(";%3.2f%%", io_u_lat_m[i]); /* disk util stats, if any */ show_disk_util(1, NULL); /* Additional output if continue_on_error set - default off*/ if (ts->continue_on_error) log_info(";%llu;%d", (unsigned long long) ts->total_err_count, ts->first_error); /* Additional output if description is set */ if (strlen(ts->description)) log_info(";%s", ts->description); log_info("\n"); } static struct json_object *show_thread_status_json(struct thread_stat *ts, struct group_run_stats *rs) { struct json_object *root, *tmp; double io_u_dist[FIO_IO_U_MAP_NR]; double io_u_lat_u[FIO_IO_U_LAT_U_NR]; double io_u_lat_m[FIO_IO_U_LAT_M_NR]; double usr_cpu, sys_cpu; int i; root = json_create_object(); json_object_add_value_string(root, "jobname", ts->name); json_object_add_value_int(root, "groupid", ts->groupid); json_object_add_value_int(root, "error", ts->error); add_ddir_status_json(ts, rs, DDIR_READ, root); add_ddir_status_json(ts, rs, DDIR_WRITE, root); add_ddir_status_json(ts, rs, DDIR_TRIM, root); /* CPU Usage */ if (ts->total_run_time) { double runt = (double) ts->total_run_time; usr_cpu = (double) ts->usr_time * 100 / runt; sys_cpu = (double) ts->sys_time * 100 / runt; } else { usr_cpu = 0; sys_cpu = 0; } json_object_add_value_float(root, "usr_cpu", usr_cpu); json_object_add_value_float(root, "sys_cpu", sys_cpu); json_object_add_value_int(root, "ctx", ts->ctx); json_object_add_value_int(root, "majf", ts->majf); json_object_add_value_int(root, "minf", ts->minf); /* Calc % distribution of IO depths, usecond, msecond latency */ stat_calc_dist(ts->io_u_map, ddir_rw_sum(ts->total_io_u), io_u_dist); stat_calc_lat_u(ts, io_u_lat_u); stat_calc_lat_m(ts, io_u_lat_m); tmp = json_create_object(); json_object_add_value_object(root, "iodepth_level", tmp); /* Only show fixed 7 I/O depth levels*/ for (i = 0; i < 7; i++) { char name[20]; if (i < 6) snprintf(name, 20, "%d", 1 << i); else snprintf(name, 20, ">=%d", 1 << i); json_object_add_value_float(tmp, (const char *)name, io_u_dist[i]); } tmp = json_create_object(); json_object_add_value_object(root, "latency_us", tmp); /* Microsecond latency */ for (i = 0; i < FIO_IO_U_LAT_U_NR; i++) { const char *ranges[] = { "2", "4", "10", "20", "50", "100", "250", "500", "750", "1000", }; json_object_add_value_float(tmp, ranges[i], io_u_lat_u[i]); } /* Millisecond latency */ tmp = json_create_object(); json_object_add_value_object(root, "latency_ms", tmp); for (i = 0; i < FIO_IO_U_LAT_M_NR; i++) { const char *ranges[] = { "2", "4", "10", "20", "50", "100", "250", "500", "750", "1000", "2000", ">=2000", }; json_object_add_value_float(tmp, ranges[i], io_u_lat_m[i]); } /* Additional output if continue_on_error set - default off*/ if (ts->continue_on_error) { json_object_add_value_int(root, "total_err", ts->total_err_count); json_object_add_value_int(root, "total_err", ts->first_error); } /* Additional output if description is set */ if (strlen(ts->description)) json_object_add_value_string(root, "desc", ts->description); return root; } static void show_thread_status_terse(struct thread_stat *ts, struct group_run_stats *rs) { if (terse_version == 2) show_thread_status_terse_v2(ts, rs); else if (terse_version == 3 || terse_version == 4) show_thread_status_terse_v3_v4(ts, rs, terse_version); else log_err("fio: bad terse version!? %d\n", terse_version); } static void sum_stat(struct io_stat *dst, struct io_stat *src, int nr) { double mean, S; if (src->samples == 0) return; dst->min_val = min(dst->min_val, src->min_val); dst->max_val = max(dst->max_val, src->max_val); /* * Compute new mean and S after the merge * */ if (nr == 1) { mean = src->mean.u.f; S = src->S.u.f; } else { double delta = src->mean.u.f - dst->mean.u.f; mean = ((src->mean.u.f * src->samples) + (dst->mean.u.f * dst->samples)) / (dst->samples + src->samples); S = src->S.u.f + dst->S.u.f + pow(delta, 2.0) * (dst->samples * src->samples) / (dst->samples + src->samples); } dst->samples += src->samples; dst->mean.u.f = mean; dst->S.u.f = S; } void sum_group_stats(struct group_run_stats *dst, struct group_run_stats *src) { int i; for (i = 0; i < DDIR_RWDIR_CNT; i++) { if (dst->max_run[i] < src->max_run[i]) dst->max_run[i] = src->max_run[i]; if (dst->min_run[i] && dst->min_run[i] > src->min_run[i]) dst->min_run[i] = src->min_run[i]; if (dst->max_bw[i] < src->max_bw[i]) dst->max_bw[i] = src->max_bw[i]; if (dst->min_bw[i] && dst->min_bw[i] > src->min_bw[i]) dst->min_bw[i] = src->min_bw[i]; dst->io_kb[i] += src->io_kb[i]; dst->agg[i] += src->agg[i]; } } void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src, int nr) { int l, k; for (l = 0; l < DDIR_RWDIR_CNT; l++) { if (!dst->unified_rw_rep) { sum_stat(&dst->clat_stat[l], &src->clat_stat[l], nr); sum_stat(&dst->slat_stat[l], &src->slat_stat[l], nr); sum_stat(&dst->lat_stat[l], &src->lat_stat[l], nr); sum_stat(&dst->bw_stat[l], &src->bw_stat[l], nr); dst->io_bytes[l] += src->io_bytes[l]; if (dst->runtime[l] < src->runtime[l]) dst->runtime[l] = src->runtime[l]; } else { sum_stat(&dst->clat_stat[0], &src->clat_stat[l], nr); sum_stat(&dst->slat_stat[0], &src->slat_stat[l], nr); sum_stat(&dst->lat_stat[0], &src->lat_stat[l], nr); sum_stat(&dst->bw_stat[0], &src->bw_stat[l], nr); dst->io_bytes[0] += src->io_bytes[l]; if (dst->runtime[0] < src->runtime[l]) dst->runtime[0] = src->runtime[l]; } } dst->usr_time += src->usr_time; dst->sys_time += src->sys_time; dst->ctx += src->ctx; dst->majf += src->majf; dst->minf += src->minf; for (k = 0; k < FIO_IO_U_MAP_NR; k++) dst->io_u_map[k] += src->io_u_map[k]; for (k = 0; k < FIO_IO_U_MAP_NR; k++) dst->io_u_submit[k] += src->io_u_submit[k]; for (k = 0; k < FIO_IO_U_MAP_NR; k++) dst->io_u_complete[k] += src->io_u_complete[k]; for (k = 0; k < FIO_IO_U_LAT_U_NR; k++) dst->io_u_lat_u[k] += src->io_u_lat_u[k]; for (k = 0; k < FIO_IO_U_LAT_M_NR; k++) dst->io_u_lat_m[k] += src->io_u_lat_m[k]; for (k = 0; k < DDIR_RWDIR_CNT; k++) { if (!dst->unified_rw_rep) { dst->total_io_u[k] += src->total_io_u[k]; dst->short_io_u[k] += src->short_io_u[k]; } else { dst->total_io_u[0] += src->total_io_u[k]; dst->short_io_u[0] += src->short_io_u[k]; } } for (k = 0; k < DDIR_RWDIR_CNT; k++) { int m; for (m = 0; m < FIO_IO_U_PLAT_NR; m++) { if (!dst->unified_rw_rep) dst->io_u_plat[k][m] += src->io_u_plat[k][m]; else dst->io_u_plat[0][m] += src->io_u_plat[k][m]; } } dst->total_run_time += src->total_run_time; dst->total_submit += src->total_submit; dst->total_complete += src->total_complete; } void init_group_run_stat(struct group_run_stats *gs) { int i; memset(gs, 0, sizeof(*gs)); for (i = 0; i < DDIR_RWDIR_CNT; i++) gs->min_bw[i] = gs->min_run[i] = ~0UL; } void init_thread_stat(struct thread_stat *ts) { int j; memset(ts, 0, sizeof(*ts)); for (j = 0; j < DDIR_RWDIR_CNT; j++) { ts->lat_stat[j].min_val = -1UL; ts->clat_stat[j].min_val = -1UL; ts->slat_stat[j].min_val = -1UL; ts->bw_stat[j].min_val = -1UL; } ts->groupid = -1; } static void __show_run_stats(void) { struct group_run_stats *runstats, *rs; struct thread_data *td; struct thread_stat *threadstats, *ts; int i, j, nr_ts, last_ts, idx; int kb_base_warned = 0; int unit_base_warned = 0; struct json_object *root = NULL; struct json_array *array = NULL; runstats = malloc(sizeof(struct group_run_stats) * (groupid + 1)); for (i = 0; i < groupid + 1; i++) init_group_run_stat(&runstats[i]); /* * find out how many threads stats we need. if group reporting isn't * enabled, it's one-per-td. */ nr_ts = 0; last_ts = -1; for_each_td(td, i) { if (!td->o.group_reporting) { nr_ts++; continue; } if (last_ts == td->groupid) continue; last_ts = td->groupid; nr_ts++; } threadstats = malloc(nr_ts * sizeof(struct thread_stat)); for (i = 0; i < nr_ts; i++) init_thread_stat(&threadstats[i]); j = 0; last_ts = -1; idx = 0; for_each_td(td, i) { if (idx && (!td->o.group_reporting || (td->o.group_reporting && last_ts != td->groupid))) { idx = 0; j++; } last_ts = td->groupid; ts = &threadstats[j]; ts->clat_percentiles = td->o.clat_percentiles; ts->percentile_precision = td->o.percentile_precision; memcpy(ts->percentile_list, td->o.percentile_list, sizeof(td->o.percentile_list)); idx++; ts->members++; if (ts->groupid == -1) { /* * These are per-group shared already */ strncpy(ts->name, td->o.name, FIO_JOBNAME_SIZE); if (td->o.description) strncpy(ts->description, td->o.description, FIO_JOBNAME_SIZE); else memset(ts->description, 0, FIO_JOBNAME_SIZE); /* * If multiple entries in this group, this is * the first member. */ ts->thread_number = td->thread_number; ts->groupid = td->groupid; /* * first pid in group, not very useful... */ ts->pid = td->pid; ts->kb_base = td->o.kb_base; ts->unit_base = td->o.unit_base; ts->unified_rw_rep = td->o.unified_rw_rep; } else if (ts->kb_base != td->o.kb_base && !kb_base_warned) { log_info("fio: kb_base differs for jobs in group, using" " %u as the base\n", ts->kb_base); kb_base_warned = 1; } else if (ts->unit_base != td->o.unit_base && !unit_base_warned) { log_info("fio: unit_base differs for jobs in group, using" " %u as the base\n", ts->unit_base); unit_base_warned = 1; } ts->continue_on_error = td->o.continue_on_error; ts->total_err_count += td->total_err_count; ts->first_error = td->first_error; if (!ts->error) { if (!td->error && td->o.continue_on_error && td->first_error) { ts->error = td->first_error; strcpy(ts->verror, td->verror); } else if (td->error) { ts->error = td->error; strcpy(ts->verror, td->verror); } } sum_thread_stats(ts, &td->ts, idx); } for (i = 0; i < nr_ts; i++) { unsigned long long bw; ts = &threadstats[i]; rs = &runstats[ts->groupid]; rs->kb_base = ts->kb_base; rs->unit_base = ts->unit_base; rs->unified_rw_rep += ts->unified_rw_rep; for (j = 0; j < DDIR_RWDIR_CNT; j++) { if (!ts->runtime[j]) continue; if (ts->runtime[j] < rs->min_run[j] || !rs->min_run[j]) rs->min_run[j] = ts->runtime[j]; if (ts->runtime[j] > rs->max_run[j]) rs->max_run[j] = ts->runtime[j]; bw = 0; if (ts->runtime[j]) { unsigned long runt = ts->runtime[j]; unsigned long long kb; kb = ts->io_bytes[j] / rs->kb_base; bw = kb * 1000 / runt; } if (bw < rs->min_bw[j]) rs->min_bw[j] = bw; if (bw > rs->max_bw[j]) rs->max_bw[j] = bw; rs->io_kb[j] += ts->io_bytes[j] / rs->kb_base; } } for (i = 0; i < groupid + 1; i++) { int ddir; rs = &runstats[i]; for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) { if (rs->max_run[ddir]) rs->agg[ddir] = (rs->io_kb[ddir] * 1000) / rs->max_run[ddir]; } } /* * don't overwrite last signal output */ if (output_format == FIO_OUTPUT_NORMAL) log_info("\n"); else if (output_format == FIO_OUTPUT_JSON) { root = json_create_object(); json_object_add_value_string(root, "fio version", fio_version_string); array = json_create_array(); json_object_add_value_array(root, "jobs", array); } for (i = 0; i < nr_ts; i++) { ts = &threadstats[i]; rs = &runstats[ts->groupid]; if (is_backend) fio_server_send_ts(ts, rs); else if (output_format == FIO_OUTPUT_TERSE) show_thread_status_terse(ts, rs); else if (output_format == FIO_OUTPUT_JSON) { struct json_object *tmp = show_thread_status_json(ts, rs); json_array_add_value_object(array, tmp); } else show_thread_status(ts, rs); } if (output_format == FIO_OUTPUT_JSON) { /* disk util stats, if any */ show_disk_util(1, root); show_idle_prof_stats(FIO_OUTPUT_JSON, root); json_print_object(root); log_info("\n"); json_free_object(root); } for (i = 0; i < groupid + 1; i++) { rs = &runstats[i]; rs->groupid = i; if (is_backend) fio_server_send_gs(rs); else if (output_format == FIO_OUTPUT_NORMAL) show_group_stats(rs); } if (is_backend) fio_server_send_du(); else if (output_format == FIO_OUTPUT_NORMAL) { show_disk_util(0, NULL); show_idle_prof_stats(FIO_OUTPUT_NORMAL, NULL); } log_info_flush(); free(runstats); free(threadstats); } void show_run_stats(void) { fio_mutex_down(stat_mutex); __show_run_stats(); fio_mutex_up(stat_mutex); } static void *__show_running_run_stats(void fio_unused *arg) { struct thread_data *td; unsigned long long *rt; struct timeval tv; int i; rt = malloc(thread_number * sizeof(unsigned long long)); fio_gettime(&tv, NULL); for_each_td(td, i) { rt[i] = mtime_since(&td->start, &tv); if (td_read(td) && td->io_bytes[DDIR_READ]) td->ts.runtime[DDIR_READ] += rt[i]; if (td_write(td) && td->io_bytes[DDIR_WRITE]) td->ts.runtime[DDIR_WRITE] += rt[i]; if (td_trim(td) && td->io_bytes[DDIR_TRIM]) td->ts.runtime[DDIR_TRIM] += rt[i]; td->update_rusage = 1; td->ts.io_bytes[DDIR_READ] = td->io_bytes[DDIR_READ]; td->ts.io_bytes[DDIR_WRITE] = td->io_bytes[DDIR_WRITE]; td->ts.io_bytes[DDIR_TRIM] = td->io_bytes[DDIR_TRIM]; td->ts.total_run_time = mtime_since(&td->epoch, &tv); } for_each_td(td, i) { if (td->rusage_sem) { td->update_rusage = 1; fio_mutex_down(td->rusage_sem); } td->update_rusage = 0; } __show_run_stats(); for_each_td(td, i) { if (td_read(td) && td->io_bytes[DDIR_READ]) td->ts.runtime[DDIR_READ] -= rt[i]; if (td_write(td) && td->io_bytes[DDIR_WRITE]) td->ts.runtime[DDIR_WRITE] -= rt[i]; if (td_trim(td) && td->io_bytes[DDIR_TRIM]) td->ts.runtime[DDIR_TRIM] -= rt[i]; } free(rt); fio_mutex_up(stat_mutex); return NULL; } /* * Called from signal handler. It _should_ be safe to just run this inline * in the sig handler, but we should be disturbing the system less by just * creating a thread to do it. */ void show_running_run_stats(void) { pthread_t thread; fio_mutex_down(stat_mutex); if (!pthread_create(&thread, NULL, __show_running_run_stats, NULL)) { pthread_detach(thread); return; } fio_mutex_up(stat_mutex); } static int status_interval_init; static struct timeval status_time; #define FIO_STATUS_FILE "/tmp/fio-dump-status" static int check_status_file(void) { struct stat sb; const char *temp_dir; char fio_status_file_path[PATH_MAX]; temp_dir = getenv("TMPDIR"); if (temp_dir == NULL) temp_dir = getenv("TEMP"); if (temp_dir == NULL) temp_dir = "/tmp"; snprintf(fio_status_file_path, sizeof(fio_status_file_path), "%s/%s", temp_dir, FIO_STATUS_FILE); if (stat(fio_status_file_path, &sb)) return 0; unlink(fio_status_file_path); return 1; } void check_for_running_stats(void) { if (status_interval) { if (!status_interval_init) { fio_gettime(&status_time, NULL); status_interval_init = 1; } else if (mtime_since_now(&status_time) >= status_interval) { show_running_run_stats(); fio_gettime(&status_time, NULL); return; } } if (check_status_file()) { show_running_run_stats(); return; } } static inline void add_stat_sample(struct io_stat *is, unsigned long data) { double val = data; double delta; if (data > is->max_val) is->max_val = data; if (data < is->min_val) is->min_val = data; delta = val - is->mean.u.f; if (delta) { is->mean.u.f += delta / (is->samples + 1.0); is->S.u.f += delta * (val - is->mean.u.f); } is->samples++; } static void __add_log_sample(struct io_log *iolog, unsigned long val, enum fio_ddir ddir, unsigned int bs, unsigned long t) { const int nr_samples = iolog->nr_samples; if (!iolog->nr_samples) iolog->avg_last = t; if (iolog->nr_samples == iolog->max_samples) { int new_size = sizeof(struct io_sample) * iolog->max_samples*2; iolog->log = realloc(iolog->log, new_size); iolog->max_samples <<= 1; } iolog->log[nr_samples].val = val; iolog->log[nr_samples].time = t; iolog->log[nr_samples].ddir = ddir; iolog->log[nr_samples].bs = bs; iolog->nr_samples++; } static inline void reset_io_stat(struct io_stat *ios) { ios->max_val = ios->min_val = ios->samples = 0; ios->mean.u.f = ios->S.u.f = 0; } static void add_log_sample(struct thread_data *td, struct io_log *iolog, unsigned long val, enum fio_ddir ddir, unsigned int bs) { unsigned long elapsed, this_window; if (!ddir_rw(ddir)) return; elapsed = mtime_since_now(&td->epoch); /* * If no time averaging, just add the log sample. */ if (!iolog->avg_msec) { __add_log_sample(iolog, val, ddir, bs, elapsed); return; } /* * Add the sample. If the time period has passed, then * add that entry to the log and clear. */ add_stat_sample(&iolog->avg_window[ddir], val); /* * If period hasn't passed, adding the above sample is all we * need to do. */ this_window = elapsed - iolog->avg_last; if (this_window < iolog->avg_msec) return; /* * Note an entry in the log. Use the mean from the logged samples, * making sure to properly round up. Only write a log entry if we * had actual samples done. */ if (iolog->avg_window[DDIR_READ].samples) { unsigned long mr; mr = iolog->avg_window[DDIR_READ].mean.u.f + 0.50; __add_log_sample(iolog, mr, DDIR_READ, 0, elapsed); } if (iolog->avg_window[DDIR_WRITE].samples) { unsigned long mw; mw = iolog->avg_window[DDIR_WRITE].mean.u.f + 0.50; __add_log_sample(iolog, mw, DDIR_WRITE, 0, elapsed); } if (iolog->avg_window[DDIR_TRIM].samples) { unsigned long mw; mw = iolog->avg_window[DDIR_TRIM].mean.u.f + 0.50; __add_log_sample(iolog, mw, DDIR_TRIM, 0, elapsed); } reset_io_stat(&iolog->avg_window[DDIR_READ]); reset_io_stat(&iolog->avg_window[DDIR_WRITE]); reset_io_stat(&iolog->avg_window[DDIR_TRIM]); iolog->avg_last = elapsed; } void add_agg_sample(unsigned long val, enum fio_ddir ddir, unsigned int bs) { struct io_log *iolog; if (!ddir_rw(ddir)) return; iolog = agg_io_log[ddir]; __add_log_sample(iolog, val, ddir, bs, mtime_since_genesis()); } static void add_clat_percentile_sample(struct thread_stat *ts, unsigned long usec, enum fio_ddir ddir) { unsigned int idx = plat_val_to_idx(usec); assert(idx < FIO_IO_U_PLAT_NR); ts->io_u_plat[ddir][idx]++; } void add_clat_sample(struct thread_data *td, enum fio_ddir ddir, unsigned long usec, unsigned int bs) { struct thread_stat *ts = &td->ts; if (!ddir_rw(ddir)) return; add_stat_sample(&ts->clat_stat[ddir], usec); if (td->clat_log) add_log_sample(td, td->clat_log, usec, ddir, bs); if (ts->clat_percentiles) add_clat_percentile_sample(ts, usec, ddir); } void add_slat_sample(struct thread_data *td, enum fio_ddir ddir, unsigned long usec, unsigned int bs) { struct thread_stat *ts = &td->ts; if (!ddir_rw(ddir)) return; add_stat_sample(&ts->slat_stat[ddir], usec); if (td->slat_log) add_log_sample(td, td->slat_log, usec, ddir, bs); } void add_lat_sample(struct thread_data *td, enum fio_ddir ddir, unsigned long usec, unsigned int bs) { struct thread_stat *ts = &td->ts; if (!ddir_rw(ddir)) return; add_stat_sample(&ts->lat_stat[ddir], usec); if (td->lat_log) add_log_sample(td, td->lat_log, usec, ddir, bs); } void add_bw_sample(struct thread_data *td, enum fio_ddir ddir, unsigned int bs, struct timeval *t) { struct thread_stat *ts = &td->ts; unsigned long spent, rate; if (!ddir_rw(ddir)) return; spent = mtime_since(&td->bw_sample_time, t); if (spent < td->o.bw_avg_time) return; /* * Compute both read and write rates for the interval. */ for (ddir = DDIR_READ; ddir < DDIR_RWDIR_CNT; ddir++) { uint64_t delta; delta = td->this_io_bytes[ddir] - td->stat_io_bytes[ddir]; if (!delta) continue; /* No entries for interval */ rate = delta * 1000 / spent / 1024; add_stat_sample(&ts->bw_stat[ddir], rate); if (td->bw_log) add_log_sample(td, td->bw_log, rate, ddir, bs); td->stat_io_bytes[ddir] = td->this_io_bytes[ddir]; } fio_gettime(&td->bw_sample_time, NULL); } void add_iops_sample(struct thread_data *td, enum fio_ddir ddir, unsigned int bs, struct timeval *t) { struct thread_stat *ts = &td->ts; unsigned long spent, iops; if (!ddir_rw(ddir)) return; spent = mtime_since(&td->iops_sample_time, t); if (spent < td->o.iops_avg_time) return; /* * Compute both read and write rates for the interval. */ for (ddir = DDIR_READ; ddir < DDIR_RWDIR_CNT; ddir++) { uint64_t delta; delta = td->this_io_blocks[ddir] - td->stat_io_blocks[ddir]; if (!delta) continue; /* No entries for interval */ iops = (delta * 1000) / spent; add_stat_sample(&ts->iops_stat[ddir], iops); if (td->iops_log) add_log_sample(td, td->iops_log, iops, ddir, bs); td->stat_io_blocks[ddir] = td->this_io_blocks[ddir]; } fio_gettime(&td->iops_sample_time, NULL); } void stat_init(void) { stat_mutex = fio_mutex_init(FIO_MUTEX_UNLOCKED); } void stat_exit(void) { /* * When we have the mutex, we know out-of-band access to it * have ended. */ fio_mutex_down(stat_mutex); fio_mutex_remove(stat_mutex); } fio-2.1.3/stat.h000066400000000000000000000172021222032232000134040ustar00rootroot00000000000000#ifndef FIO_STAT_H #define FIO_STAT_H #include "iolog.h" struct group_run_stats { uint64_t max_run[DDIR_RWDIR_CNT], min_run[DDIR_RWDIR_CNT]; uint64_t max_bw[DDIR_RWDIR_CNT], min_bw[DDIR_RWDIR_CNT]; uint64_t io_kb[DDIR_RWDIR_CNT]; uint64_t agg[DDIR_RWDIR_CNT]; uint32_t kb_base; uint32_t unit_base; uint32_t groupid; uint32_t unified_rw_rep; }; /* * How many depth levels to log */ #define FIO_IO_U_MAP_NR 7 #define FIO_IO_U_LAT_U_NR 10 #define FIO_IO_U_LAT_M_NR 12 /* * Aggregate clat samples to report percentile(s) of them. * * EXECUTIVE SUMMARY * * FIO_IO_U_PLAT_BITS determines the maximum statistical error on the * value of resulting percentiles. The error will be approximately * 1/2^(FIO_IO_U_PLAT_BITS+1) of the value. * * FIO_IO_U_PLAT_GROUP_NR and FIO_IO_U_PLAT_BITS determine the maximum * range being tracked for latency samples. The maximum value tracked * accurately will be 2^(GROUP_NR + PLAT_BITS -1) microseconds. * * FIO_IO_U_PLAT_GROUP_NR and FIO_IO_U_PLAT_BITS determine the memory * requirement of storing those aggregate counts. The memory used will * be (FIO_IO_U_PLAT_GROUP_NR * 2^FIO_IO_U_PLAT_BITS) * sizeof(int) * bytes. * * FIO_IO_U_PLAT_NR is the total number of buckets. * * DETAILS * * Suppose the clat varies from 0 to 999 (usec), the straightforward * method is to keep an array of (999 + 1) buckets, in which a counter * keeps the count of samples which fall in the bucket, e.g., * {[0],[1],...,[999]}. However this consumes a huge amount of space, * and can be avoided if an approximation is acceptable. * * One such method is to let the range of the bucket to be greater * than one. This method has low accuracy when the value is small. For * example, let the buckets be {[0,99],[100,199],...,[900,999]}, and * the represented value of each bucket be the mean of the range. Then * a value 0 has an round-off error of 49.5. To improve on this, we * use buckets with non-uniform ranges, while bounding the error of * each bucket within a ratio of the sample value. A simple example * would be when error_bound = 0.005, buckets are { * {[0],[1],...,[99]}, {[100,101],[102,103],...,[198,199]},.., * {[900,909],[910,919]...} }. The total range is partitioned into * groups with different ranges, then buckets with uniform ranges. An * upper bound of the error is (range_of_bucket/2)/value_of_bucket * * For better efficiency, we implement this using base two. We group * samples by their Most Significant Bit (MSB), extract the next M bit * of them as an index within the group, and discard the rest of the * bits. * * E.g., assume a sample 'x' whose MSB is bit n (starting from bit 0), * and use M bit for indexing * * | n | M bits | bit (n-M-1) ... bit 0 | * * Because x is at least 2^n, and bit 0 to bit (n-M-1) is at most * (2^(n-M) - 1), discarding bit 0 to (n-M-1) makes the round-off * error * * 2^(n-M)-1 2^(n-M) 1 * e <= --------- <= ------- = --- * 2^n 2^n 2^M * * Furthermore, we use "mean" of the range to represent the bucket, * the error e can be lowered by half to 1 / 2^(M+1). By using M bits * as the index, each group must contains 2^M buckets. * * E.g. Let M (FIO_IO_U_PLAT_BITS) be 6 * Error bound is 1/2^(6+1) = 0.0078125 (< 1%) * * Group MSB #discarded range of #buckets * error_bits value * ---------------------------------------------------------------- * 0* 0~5 0 [0,63] 64 * 1* 6 0 [64,127] 64 * 2 7 1 [128,255] 64 * 3 8 2 [256,511] 64 * 4 9 3 [512,1023] 64 * ... ... ... [...,...] ... * 18 23 17 [8838608,+inf]** 64 * * * Special cases: when n < (M-1) or when n == (M-1), in both cases, * the value cannot be rounded off. Use all bits of the sample as * index. * * ** If a sample's MSB is greater than 23, it will be counted as 23. */ #define FIO_IO_U_PLAT_BITS 6 #define FIO_IO_U_PLAT_VAL (1 << FIO_IO_U_PLAT_BITS) #define FIO_IO_U_PLAT_GROUP_NR 19 #define FIO_IO_U_PLAT_NR (FIO_IO_U_PLAT_GROUP_NR * FIO_IO_U_PLAT_VAL) #define FIO_IO_U_LIST_MAX_LEN 20 /* The size of the default and user-specified list of percentiles */ #define MAX_PATTERN_SIZE 512 #define FIO_JOBNAME_SIZE 128 #define FIO_VERROR_SIZE 128 struct thread_stat { char name[FIO_JOBNAME_SIZE]; char verror[FIO_VERROR_SIZE]; uint32_t error; uint32_t thread_number; uint32_t groupid; uint32_t pid; char description[FIO_JOBNAME_SIZE]; uint32_t members; uint32_t unified_rw_rep; /* * bandwidth and latency stats */ struct io_stat clat_stat[DDIR_RWDIR_CNT]; /* completion latency */ struct io_stat slat_stat[DDIR_RWDIR_CNT]; /* submission latency */ struct io_stat lat_stat[DDIR_RWDIR_CNT]; /* total latency */ struct io_stat bw_stat[DDIR_RWDIR_CNT]; /* bandwidth stats */ struct io_stat iops_stat[DDIR_RWDIR_CNT]; /* IOPS stats */ /* * fio system usage accounting */ uint64_t usr_time; uint64_t sys_time; uint64_t ctx; uint64_t minf, majf; /* * IO depth and latency stats */ uint64_t clat_percentiles; uint64_t percentile_precision; fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN]; uint32_t io_u_map[FIO_IO_U_MAP_NR]; uint32_t io_u_submit[FIO_IO_U_MAP_NR]; uint32_t io_u_complete[FIO_IO_U_MAP_NR]; uint32_t io_u_lat_u[FIO_IO_U_LAT_U_NR]; uint32_t io_u_lat_m[FIO_IO_U_LAT_M_NR]; uint32_t io_u_plat[DDIR_RWDIR_CNT][FIO_IO_U_PLAT_NR]; uint64_t total_io_u[3]; uint64_t short_io_u[3]; uint64_t total_submit; uint64_t total_complete; uint64_t io_bytes[DDIR_RWDIR_CNT]; uint64_t runtime[DDIR_RWDIR_CNT]; uint64_t total_run_time; /* * IO Error related stats */ uint16_t continue_on_error; uint64_t total_err_count; uint32_t first_error; uint32_t kb_base; uint32_t unit_base; }; struct jobs_eta { uint32_t nr_running; uint32_t nr_ramp; uint32_t nr_pending; uint32_t nr_setting_up; uint32_t files_open; uint32_t m_rate[DDIR_RWDIR_CNT], t_rate[DDIR_RWDIR_CNT]; uint32_t m_iops[DDIR_RWDIR_CNT], t_iops[DDIR_RWDIR_CNT]; uint32_t rate[DDIR_RWDIR_CNT]; uint32_t iops[DDIR_RWDIR_CNT]; uint64_t elapsed_sec; uint64_t eta_sec; uint32_t is_pow2; uint32_t unit_base; /* * Network 'copy' of run_str[] */ uint32_t nr_threads; uint8_t run_str[]; }; extern void stat_init(void); extern void stat_exit(void); extern void show_thread_status(struct thread_stat *ts, struct group_run_stats *rs); extern void show_group_stats(struct group_run_stats *rs); extern int calc_thread_status(struct jobs_eta *je, int force); extern void display_thread_status(struct jobs_eta *je); extern void show_run_stats(void); extern void show_running_run_stats(void); extern void check_for_running_stats(void); extern void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src, int nr); extern void sum_group_stats(struct group_run_stats *dst, struct group_run_stats *src); extern void init_thread_stat(struct thread_stat *ts); extern void init_group_run_stat(struct group_run_stats *gs); extern void eta_to_str(char *str, unsigned long eta_sec); extern int calc_lat(struct io_stat *is, unsigned long *min, unsigned long *max, double *mean, double *dev); extern unsigned int calc_clat_percentiles(unsigned int *io_u_plat, unsigned long nr, fio_fp64_t *plist, unsigned int **output, unsigned int *maxv, unsigned int *minv); extern void stat_calc_lat_m(struct thread_stat *ts, double *io_u_lat); extern void stat_calc_lat_u(struct thread_stat *ts, double *io_u_lat); extern void stat_calc_dist(unsigned int *map, unsigned long total, double *io_u_dist); static inline int usec_to_msec(unsigned long *min, unsigned long *max, double *mean, double *dev) { if (*min > 1000 && *max > 1000 && *mean > 1000.0 && *dev > 1000.0) { *min /= 1000; *max /= 1000; *mean /= 1000.0; *dev /= 1000.0; return 0; } return 1; } #endif fio-2.1.3/t/000077500000000000000000000000001222032232000125215ustar00rootroot00000000000000fio-2.1.3/t/axmap.c000066400000000000000000000044541222032232000140020ustar00rootroot00000000000000#include #include #include #include #include #include #include "../lib/lfsr.h" #include "../lib/axmap.h" void *smalloc(size_t size) { return malloc(size); } void sfree(void *ptr) { free(ptr); } static int test_regular(size_t size, int seed) { struct fio_lfsr lfsr; struct axmap *map; size_t osize; uint64_t ff; int err; printf("Using %llu entries...", (unsigned long long) size); fflush(stdout); lfsr_init(&lfsr, size, seed, seed & 0xF); map = axmap_new(size); osize = size; err = 0; while (size--) { uint64_t val; if (lfsr_next(&lfsr, &val, osize)) { printf("lfsr: short loop\n"); err = 1; break; } if (axmap_isset(map, val)) { printf("bit already set\n"); err = 1; break; } axmap_set(map, val); if (!axmap_isset(map, val)) { printf("bit not set\n"); err = 1; break; } } if (err) return err; ff = axmap_next_free(map, osize); if (ff != (uint64_t) -1ULL) { printf("axmap_next_free broken: got %llu\n", (unsigned long long) ff); return 1; } printf("pass!\n"); axmap_free(map); return 0; } static int test_multi(size_t size, unsigned int bit_off) { unsigned int map_size = size; struct axmap *map; uint64_t val = bit_off; int i, err; printf("Test multi %llu entries %u offset...", (unsigned long long) size, bit_off); fflush(stdout); map = axmap_new(map_size); while (val + 128 <= map_size) { err = 0; for (i = val; i < val + 128; i++) { if (axmap_isset(map, val + i)) { printf("bit already set\n"); err = 1; break; } } if (err) break; err = axmap_set_nr(map, val, 128); if (err != 128) { printf("only set %u bits\n", err); break; } err = 0; for (i = 0; i < 128; i++) { if (!axmap_isset(map, val + i)) { printf("bit not set: %llu\n", (unsigned long long) val + i); err = 1; break; } } val += 128; if (err) break; } if (!err) printf("pass!\n"); axmap_free(map); return err; } int main(int argc, char *argv[]) { size_t size = (1UL << 23) - 200; int seed = 1; if (argc > 1) { size = strtoul(argv[1], NULL, 10); if (argc > 2) seed = strtoul(argv[2], NULL, 10); } if (test_regular(size, seed)) return 1; if (test_multi(size, 0)) return 2; if (test_multi(size, 17)) return 3; return 0; } fio-2.1.3/t/genzipf.c000066400000000000000000000156741222032232000143440ustar00rootroot00000000000000/* * Generate/analyze pareto/zipf distributions to better understand * what an access pattern would look like. * * For instance, the following would generate a zipf distribution * with theta 1.2, using 100,000 values and split the reporting into * 20 buckets: * * t/genzipf zipf 1.2 100000 20 * * Only the distribution type (zipf or pareto) and spread input need * to be given, if not given defaults are used. * */ #include #include #include #include #include #include "../lib/zipf.h" #include "../flist.h" #include "../hash.h" #define DEF_NR 1000000 #define DEF_NR_OUTPUT 23 struct node { struct flist_head list; unsigned long long val; unsigned long hits; }; static struct flist_head *hash; static unsigned long hash_bits = 24; static unsigned long hash_size = 1 << 24; enum { TYPE_NONE = 0, TYPE_ZIPF, TYPE_PARETO, }; static const char *dist_types[] = { "None", "Zipf", "Pareto" }; static int dist_type = TYPE_ZIPF; static unsigned long gb_size = 500; static unsigned long block_size = 4096; static unsigned long output_nranges = DEF_NR_OUTPUT; static double percentage; static double dist_val; static int output_csv = 0; #define DEF_ZIPF_VAL 1.2 #define DEF_PARETO_VAL 0.3 static struct node *hash_lookup(unsigned long long val) { struct flist_head *l = &hash[hash_long(val, hash_bits)]; struct flist_head *entry; struct node *n; flist_for_each(entry, l) { n = flist_entry(entry, struct node, list); if (n->val == val) return n; } return NULL; } static struct node *hash_insert(struct node *n, unsigned long long val) { struct flist_head *l = &hash[hash_long(val, hash_bits)]; n->val = val; n->hits = 1; flist_add_tail(&n->list, l); return n; } static void usage(void) { printf("genzipf: test zipf/pareto values for fio input\n"); printf("\t-h\tThis help screen\n"); printf("\t-p\tGenerate size of data set that are hit by this percentage\n"); printf("\t-t\tDistribution type (zipf or pareto)\n"); printf("\t-i\tDistribution algorithm input (zipf theta or pareto power)\n"); printf("\t-b\tBlock size of a given range (in bytes)\n"); printf("\t-g\tSize of data set (in gigabytes)\n"); printf("\t-o\tNumber of output columns\n"); printf("\t-c\tOutput ranges in CSV format\n"); } static int parse_options(int argc, char *argv[]) { const char *optstring = "t:g:i:o:b:p:ch"; int c, dist_val_set = 0; while ((c = getopt(argc, argv, optstring)) != -1) { switch (c) { case 'h': usage(); return 1; case 'p': percentage = atof(optarg); break; case 'b': block_size = strtoul(optarg, NULL, 10); break; case 't': if (!strncmp(optarg, "zipf", 4)) dist_type = TYPE_ZIPF; else if (!strncmp(optarg, "pareto", 6)) dist_type = TYPE_PARETO; else { printf("wrong dist type: %s\n", optarg); return 1; } break; case 'g': gb_size = strtoul(optarg, NULL, 10); break; case 'i': dist_val = atof(optarg); dist_val_set = 1; break; case 'o': output_nranges = strtoul(optarg, NULL, 10); break; case 'c': output_csv = 1; break; default: printf("bad option %c\n", c); return 1; } } if (dist_type == TYPE_PARETO) { if ((dist_val >= 1.00 || dist_val < 0.00)) { printf("pareto input must be > 0.00 and < 1.00\n"); return 1; } if (!dist_val_set) dist_val = DEF_PARETO_VAL; } else if (dist_type == TYPE_ZIPF) { if (dist_val == 1.0) { printf("zipf input must be different than 1.0\n"); return 1; } if (!dist_val_set) dist_val = DEF_ZIPF_VAL; } return 0; } struct output_sum { double output; unsigned int nranges; }; static int node_cmp(const void *p1, const void *p2) { const struct node *n1 = p1; const struct node *n2 = p2; return n2->hits - n1->hits; } int main(int argc, char *argv[]) { unsigned long offset; unsigned long i, j, k, nr_vals, cur_vals, interval, total_vals, nnodes; unsigned long long nranges; struct output_sum *output_sums; struct node *nodes; double perc, perc_i; struct zipf_state zs; if (parse_options(argc, argv)) return 1; if( !output_csv ) printf("Generating %s distribution with %f input and %lu GB size and %lu block_size.\n", dist_types[dist_type], dist_val, gb_size, block_size); nranges = gb_size * 1024 * 1024 * 1024ULL; nranges /= block_size; if (dist_type == TYPE_ZIPF) zipf_init(&zs, nranges, dist_val, 1); else pareto_init(&zs, nranges, dist_val, 1); hash_bits = 0; hash_size = nranges; while ((hash_size >>= 1) != 0) hash_bits++; hash_size = 1 << hash_bits; hash = malloc(hash_size * sizeof(struct flist_head)); for (i = 0; i < hash_size; i++) INIT_FLIST_HEAD(&hash[i]); nodes = malloc(nranges * sizeof(struct node)); for (nr_vals = i = j = 0; i < nranges; i++) { struct node *n; if (dist_type == TYPE_ZIPF) offset = zipf_next(&zs); else offset = pareto_next(&zs); n = hash_lookup(offset); if (n) n->hits++; else { hash_insert(&nodes[j], offset); j++; } nr_vals++; } qsort(nodes, j, sizeof(struct node), node_cmp); nnodes = j; nr_vals = nnodes; if (output_csv) { printf("rank, count\n"); for (k = 0; k < nnodes; k++) printf("%lu, %lu\n", k, nodes[k].hits); } else { interval = (nr_vals + output_nranges - 1) / output_nranges; output_sums = malloc(output_nranges * sizeof(struct output_sum)); for (i = 0; i < output_nranges; i++) { output_sums[i].output = 0.0; output_sums[i].nranges = 1; } total_vals = i = j = cur_vals = 0; for (k = 0; k < nnodes; k++) { struct output_sum *os = &output_sums[j]; struct node *node = &nodes[k]; if (i >= interval) { os->output = (double)(cur_vals + 1) / (double)nranges; os->output *= 100.0; j++; cur_vals = node->hits; interval += (nr_vals + output_nranges - 1) / output_nranges; } else { cur_vals += node->hits; os->nranges += node->hits; } i++; total_vals += node->hits; if (percentage) { unsigned long blocks = percentage * nranges / 100; if (total_vals >= blocks) { double cs = i * block_size / (1024 * 1024); char p = 'M'; if (cs > 1024.0) { cs /= 1024.0; p = 'G'; } if (cs > 1024.0) { cs /= 1024.0; p = 'T'; } printf("%.2f%% of hits satisfied in %.3f%cB of cache\n", percentage, cs, p); percentage = 0.0; } } } perc_i = 100.0 / (double)output_nranges; perc = 0.0; printf("\n Rows Hits No Hits Size\n"); printf("--------------------------------------------------------\n"); for (i = 0; i < j; i++) { struct output_sum *os = &output_sums[i]; double gb = (double)os->nranges * block_size / 1024.0; char p = 'K'; if (gb > 1024.0) { p = 'M'; gb /= 1024.0; } if (gb > 1024.0) { p = 'G'; gb /= 1024.0; } perc += perc_i; printf("%s %6.2f%%\t%6.2f%%\t\t%8u\t%6.2f%c\n", i ? "|->" : "Top", perc, os->output, os->nranges, gb, p); } free(output_sums); } free(hash); free(nodes); return 0; } fio-2.1.3/t/ieee754.c000066400000000000000000000005501222032232000140340ustar00rootroot00000000000000#include #include "../lib/ieee754.h" static double values[] = { -17.23, 17.23, 123.4567, 98765.4321, 0.0 }; int main(int argc, char *argv[]) { uint64_t i; double f; int j; j = 0; do { i = fio_double_to_uint64(values[j]); f = fio_uint64_to_double(i); printf("%f -> %f\n", values[j], f); j++; } while (values[j] != 0.0); return 0; } fio-2.1.3/t/jobs/000077500000000000000000000000001222032232000134565ustar00rootroot00000000000000fio-2.1.3/t/jobs/t0001-52c58027.fio000066400000000000000000000001521222032232000157140ustar00rootroot00000000000000#Commit 52c580272d87d2b9b8a65d317bf7c2d432a30fec [foo] size=20000 bsrange=1k-4k rw=randread ioengine=null fio-2.1.3/t/jobs/t0002-13af05ae-post000066400000000000000000000004501222032232000163330ustar00rootroot00000000000000[global] ioengine=libaio direct=1 filename=/dev/fioa iodepth=128 size=1G loops=1 group_reporting=1 readwrite=read do_verify=1 verify=md5 verify_fatal=1 numjobs=1 thread bssplit=512/50:1M/50 [thread0] offset=0G [thread-mix0] offset=4G size=1G readwrite=rw bsrange=512:1M fio-2.1.3/t/jobs/t0002-13af05ae-pre000066400000000000000000000004311222032232000161330ustar00rootroot00000000000000[global] ioengine=libaio direct=1 filename=/dev/fioa iodepth=128 size=1G loops=1 group_reporting=1 readwrite=write do_verify=0 verify=md5 numjobs=1 thread bssplit=512/50:1M/50 [thread0] offset=0G [thread-mix0] offset=4G readwrite=rw size=1G bsrange=512:1M fio-2.1.3/t/jobs/t0003-0ae2c6e1-post.fio000066400000000000000000000005101222032232000171060ustar00rootroot00000000000000# Expected result: verify fails. # Buggy result: fio segfaults [global] ioengine=libaio direct=1 filename=/tmp/foo iodepth=128 size=1M loops=1 group_reporting=1 readwrite=read do_verify=1 verify=md5 verify_fatal=1 numjobs=1 thread verify_dump=1 bs=4k [large_reads] offset=0G blocksize=1M [small_reads] offset=1G blocksize=512 fio-2.1.3/t/jobs/t0003-0ae2c6e1-pre.fio000066400000000000000000000003701222032232000167130ustar00rootroot00000000000000[global] ioengine=libaio direct=1 filename=/tmp/foo iodepth=128 size=10M loops=1 group_reporting=1 readwrite=write do_verify=0 verify=md5 numjobs=1 thread verify_dump=1 [small_writes] offset=0G blocksize=512 [large_writes] offset=1G blocksize=1M fio-2.1.3/t/jobs/t0004-8a99fdf6.fio000066400000000000000000000005611222032232000161640ustar00rootroot00000000000000# Expected result: fio runs to completion # Buggy result: fio segfaults [global] ioengine=libaio direct=1 filename=/tmp/foo iodepth=128 size=10M loops=1 group_reporting=1 readwrite=write do_verify=0 verify=md5 numjobs=1 thread verify_dump=1 [small_writes] offset=0G blocksize=512 verify_interval=1M [large_writes] stonewall offset=1G blocksize=1M verify_interval=512 fio-2.1.3/t/jobs/t0005-f7078f7b.fio000066400000000000000000000003061222032232000160740ustar00rootroot00000000000000# Expected result: fio reads and writes 100m # Buggy result: fio reads and writes ~100m/2 [global] bs=4k ioengine=sync size=100m direct=1 filename=xxx [write] verify=md5 verify_backlog=32 rw=write fio-2.1.3/t/jobs/t0006-82af2a7c.fio000066400000000000000000000004631222032232000161440ustar00rootroot00000000000000# Expected results: workload runs and switches between 'm' and 'V' state # Buggy result: workload stays in 'm' mode, never doing actual verifies [global] rw=randrw bs=4k direct=1 ioengine=libaio iodepth=32 verify=meta verify_backlog=1024 verify_fatal=1 [ver-test] filename=foo size=4g verify_pattern=0xaaa fio-2.1.3/t/jobs/t0007-37cf9e3c.fio000066400000000000000000000002621222032232000161530ustar00rootroot00000000000000# Expected result: fio reads 87040KB of data # Buggy result: fio reads the full 128MB of data [foo] size=128mb rw=read:512k bs=1m time_based norandommap write_iolog=log direct=1 fio-2.1.3/t/jobs/t0008-ae2fafc8.fio000066400000000000000000000002771222032232000163130ustar00rootroot00000000000000# Expected result: fio writes 16MB, reads 16+16MB # Buggy result: fio writes 16MB, reads ~21MB [global] bs=4k verify=crc32c rw=readwrite direct=1 [foo] size=32m do_verify=1 verify_backlog=1 fio-2.1.3/t/lfsr-test.c000066400000000000000000000063631222032232000146200ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "../lib/lfsr.h" void usage() { printf("Usage: lfsr-test 0x [seed] [spin] [verify]\n"); printf("-------------------------------------------------------------\n"); printf("*numbers: how many random numbers to produce (in hex)\n" "seed: initial value\n" "spin: how many iterations before we produce a number\n" "verify: check if LFSR has iterated correctly\n\n" "Only is required. The rest are evaluated to 0 or false\n" "Elapsed/mean time and verification results are printed at the" "end of the test\n"); } int main(int argc, char *argv[]) { int r; struct timespec start, end; struct fio_lfsr *fl; int verify = 0; unsigned int spin = 0; uint64_t seed = 0; uint64_t numbers; uint64_t v_size; uint64_t i; void *v = NULL, *v_start; double total, mean; /* Read arguments */ switch (argc) { case 5: if (strncmp(argv[4], "verify", 7) == 0) verify = 1; case 4: spin = atoi(argv[3]); case 3: seed = atol(argv[2]); case 2: numbers = strtol(argv[1], NULL, 16); break; default: usage(); return 1; } /* Initialize LFSR */ fl = malloc(sizeof(struct fio_lfsr)); if (!fl) { perror("malloc"); return 1; } r = lfsr_init(fl, numbers, seed, spin); if (r) { printf("Initialization failed.\n"); return r; } /* Print specs */ printf("LFSR specs\n"); printf("==========================\n"); printf("Size is %u\n", 64 - __builtin_clzl(fl->cached_bit)); printf("Max val is %lu\n", fl->max_val); printf("XOR-mask is 0x%lX\n", fl->xormask); printf("Seed is %lu\n", fl->last_val); printf("Spin is %u\n", fl->spin); printf("Cycle length is %lu\n", fl->cycle_length); /* Create verification table */ if (verify) { v_size = numbers * sizeof(uint8_t); v = malloc(v_size); memset(v, 0, v_size); printf("\nVerification table is %lf KBs\n", (double)(v_size) / 1024); } v_start = v; /* * Iterate over a tight loop until we have produced all the requested * numbers. Verifying the results should introduce some small yet not * negligible overhead. */ fprintf(stderr, "\nTest initiated... "); clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start); while (!lfsr_next(fl, &i, fl->max_val)) { if (verify) *(uint8_t *)(v + i) += 1; } clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end); fprintf(stderr, "finished.\n"); /* Check if all expected numbers within range have been calculated */ r = 0; if (verify) { fprintf(stderr, "Verifying results... "); for (i = 0; i < numbers; i++) { if (*(uint8_t *)(v + i) != 1) { fprintf(stderr, "failed (%lu = %d).\n", i, *(uint8_t *)(v + i)); r = 1; break; } } if (!r) fprintf(stderr, "OK!\n"); } /* Calculate elapsed time and mean time per number */ total = (end.tv_sec - start.tv_sec) * pow(10,9) + end.tv_nsec - start.tv_nsec; mean = total / fl->num_vals; printf("\nTime results "); if (verify) printf("(slower due to verification)"); printf("\n==============================\n"); printf("Elapsed: %lf s\n", total / pow(10,9)); printf("Mean: %lf ns\n", mean); free(v_start); free(fl); return r; } fio-2.1.3/t/log.c000066400000000000000000000011211222032232000134410ustar00rootroot00000000000000#include #include #include "../minmax.h" int log_err(const char *format, ...) { char buffer[1024]; va_list args; size_t len; va_start(args, format); len = vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); len = min(len, sizeof(buffer) - 1); return fwrite(buffer, len, 1, stderr); } int log_info(const char *format, ...) { char buffer[1024]; va_list args; size_t len; va_start(args, format); len = vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); len = min(len, sizeof(buffer) - 1); return fwrite(buffer, len, 1, stdout); } fio-2.1.3/t/stest.c000066400000000000000000000026751222032232000140410ustar00rootroot00000000000000#include #include #include #include "../smalloc.h" #include "../flist.h" FILE *f_err; struct timeval *fio_tv = NULL; unsigned int fio_debug = 0; #define MAGIC1 0xa9b1c8d2 #define MAGIC2 0xf0a1e9b3 #define LOOPS 32 struct elem { unsigned int magic1; struct flist_head list; unsigned int magic2; }; FLIST_HEAD(list); static int do_rand_allocs(void) { unsigned int size, nr, rounds = 0; unsigned long total; struct elem *e; while (rounds++ < LOOPS) { #ifdef STEST_SEED srand(MAGIC1); #endif nr = total = 0; while (total < 128*1024*1024UL) { size = 8 * sizeof(struct elem) + (int) (999.0 * (rand() / (RAND_MAX + 1.0))); e = smalloc(size); if (!e) { printf("fail at %lu, size %u\n", total, size); break; } e->magic1 = MAGIC1; e->magic2 = MAGIC2; total += size; flist_add_tail(&e->list, &list); nr++; } printf("Got items: %u\n", nr); while (!flist_empty(&list)) { e = flist_entry(list.next, struct elem, list); assert(e->magic1 == MAGIC1); assert(e->magic2 == MAGIC2); flist_del(&e->list); sfree(e); } } return 0; } static int do_specific_alloc(unsigned long size) { void *ptr; ptr = smalloc(size); sfree(ptr); return 0; } int main(int argc, char *argv[]) { f_err = stderr; sinit(); do_rand_allocs(); /* smalloc bug, commit 271067a6 */ do_specific_alloc(671386584); scleanup(); return 0; } void __dprint(int type, const char *str, ...) { } fio-2.1.3/td_error.c000066400000000000000000000016171222032232000142470ustar00rootroot00000000000000#include "fio.h" #include "io_ddir.h" #include "td_error.h" static int __NON_FATAL_ERR[] = { EIO, EILSEQ }; enum error_type_bit td_error_type(enum fio_ddir ddir, int err) { if (err == EILSEQ) return ERROR_TYPE_VERIFY_BIT; if (ddir == DDIR_READ) return ERROR_TYPE_READ_BIT; return ERROR_TYPE_WRITE_BIT; } int td_non_fatal_error(struct thread_data *td, enum error_type_bit etype, int err) { unsigned int i; if (!td->o.ignore_error[etype]) { td->o.ignore_error[etype] = __NON_FATAL_ERR; td->o.ignore_error_nr[etype] = sizeof(__NON_FATAL_ERR) / sizeof(int); } if (!(td->o.continue_on_error & (1 << etype))) return 0; for (i = 0; i < td->o.ignore_error_nr[etype]; i++) if (td->o.ignore_error[etype][i] == err) return 1; return 0; } void update_error_count(struct thread_data *td, int err) { td->total_err_count++; if (td->total_err_count == 1) td->first_error = err; } fio-2.1.3/td_error.h000066400000000000000000000013201222032232000142430ustar00rootroot00000000000000#ifndef FIO_TD_ERROR_H #define FIO_TD_ERROR_H /* * What type of errors to continue on when continue_on_error is used */ enum error_type_bit { ERROR_TYPE_READ_BIT = 0, ERROR_TYPE_WRITE_BIT = 1, ERROR_TYPE_VERIFY_BIT = 2, ERROR_TYPE_CNT = 3, }; enum error_type { ERROR_TYPE_NONE = 0, ERROR_TYPE_READ = 1 << ERROR_TYPE_READ_BIT, ERROR_TYPE_WRITE = 1 << ERROR_TYPE_WRITE_BIT, ERROR_TYPE_VERIFY = 1 << ERROR_TYPE_VERIFY_BIT, ERROR_TYPE_ANY = 0xffff, }; enum error_type_bit td_error_type(enum fio_ddir ddir, int err); int td_non_fatal_error(struct thread_data *td, enum error_type_bit etype, int err); void update_error_count(struct thread_data *td, int err); #endif fio-2.1.3/thread_options.h000066400000000000000000000250041222032232000154520ustar00rootroot00000000000000#ifndef FIO_THREAD_OPTIONS_H #define FIO_THREAD_OPTIONS_H #include "arch/arch.h" #include "os/os.h" #include "stat.h" #include "gettime.h" #include "lib/ieee754.h" #include "td_error.h" /* * What type of allocation to use for io buffers */ enum fio_memtype { MEM_MALLOC = 0, /* ordinary malloc */ MEM_SHM, /* use shared memory segments */ MEM_SHMHUGE, /* use shared memory segments with huge pages */ MEM_MMAP, /* use anonynomous mmap */ MEM_MMAPHUGE, /* memory mapped huge file */ }; #define ERROR_STR_MAX 128 #define BSSPLIT_MAX 64 struct bssplit { uint32_t bs; uint32_t perc; }; struct thread_options { int pad; char *description; char *name; char *directory; char *filename; char *filename_format; char *opendir; char *ioengine; char *mmapfile; enum td_ddir td_ddir; unsigned int rw_seq; unsigned int kb_base; unsigned int unit_base; unsigned int ddir_seq_nr; long ddir_seq_add; unsigned int iodepth; unsigned int iodepth_low; unsigned int iodepth_batch; unsigned int iodepth_batch_complete; unsigned long long size; unsigned int size_percent; unsigned int fill_device; unsigned long long file_size_low; unsigned long long file_size_high; unsigned long long start_offset; unsigned int bs[DDIR_RWDIR_CNT]; unsigned int ba[DDIR_RWDIR_CNT]; unsigned int min_bs[DDIR_RWDIR_CNT]; unsigned int max_bs[DDIR_RWDIR_CNT]; struct bssplit *bssplit[DDIR_RWDIR_CNT]; unsigned int bssplit_nr[DDIR_RWDIR_CNT]; int *ignore_error[ERROR_TYPE_CNT]; unsigned int ignore_error_nr[ERROR_TYPE_CNT]; unsigned int error_dump; unsigned int nr_files; unsigned int open_files; enum file_lock_mode file_lock_mode; unsigned int odirect; unsigned int invalidate_cache; unsigned int create_serialize; unsigned int create_fsync; unsigned int create_on_open; unsigned int create_only; unsigned int end_fsync; unsigned int pre_read; unsigned int sync_io; unsigned int verify; unsigned int do_verify; unsigned int verifysort; unsigned int verifysort_nr; unsigned int verify_interval; unsigned int verify_offset; char verify_pattern[MAX_PATTERN_SIZE]; unsigned int verify_pattern_bytes; unsigned int verify_fatal; unsigned int verify_dump; unsigned int verify_async; unsigned long long verify_backlog; unsigned int verify_batch; unsigned int experimental_verify; unsigned int use_thread; unsigned int unlink; unsigned int do_disk_util; unsigned int override_sync; unsigned int rand_repeatable; unsigned int use_os_rand; unsigned int log_avg_msec; unsigned int norandommap; unsigned int softrandommap; unsigned int bs_unaligned; unsigned int fsync_on_close; unsigned int bs_is_seq_rand; unsigned int random_distribution; fio_fp64_t zipf_theta; fio_fp64_t pareto_h; unsigned int random_generator; unsigned int perc_rand[DDIR_RWDIR_CNT]; unsigned int hugepage_size; unsigned int rw_min_bs; unsigned int thinktime; unsigned int thinktime_spin; unsigned int thinktime_blocks; unsigned int fsync_blocks; unsigned int fdatasync_blocks; unsigned int barrier_blocks; unsigned long long start_delay; unsigned long long timeout; unsigned long long ramp_time; unsigned int overwrite; unsigned int bw_avg_time; unsigned int iops_avg_time; unsigned int loops; unsigned long long zone_range; unsigned long long zone_size; unsigned long long zone_skip; unsigned long long lockmem; enum fio_memtype mem_type; unsigned int mem_align; unsigned int max_latency; unsigned int stonewall; unsigned int new_group; unsigned int numjobs; os_cpu_mask_t cpumask; unsigned int cpumask_set; os_cpu_mask_t verify_cpumask; unsigned int verify_cpumask_set; #ifdef CONFIG_LIBNUMA struct bitmask *numa_cpunodesmask; unsigned int numa_cpumask_set; unsigned short numa_mem_mode; unsigned int numa_mem_prefer_node; struct bitmask *numa_memnodesmask; unsigned int numa_memmask_set; #endif unsigned int iolog; unsigned int rwmixcycle; unsigned int rwmix[2]; unsigned int nice; unsigned int ioprio; unsigned int ioprio_class; unsigned int file_service_type; unsigned int group_reporting; unsigned int fadvise_hint; enum fio_fallocate_mode fallocate_mode; unsigned int zero_buffers; unsigned int refill_buffers; unsigned int scramble_buffers; unsigned int compress_percentage; unsigned int compress_chunk; unsigned int time_based; unsigned int disable_lat; unsigned int disable_clat; unsigned int disable_slat; unsigned int disable_bw; unsigned int unified_rw_rep; unsigned int gtod_reduce; unsigned int gtod_cpu; unsigned int gtod_offload; enum fio_cs clocksource; unsigned int no_stall; unsigned int trim_percentage; unsigned int trim_batch; unsigned int trim_zero; unsigned long long trim_backlog; unsigned int clat_percentiles; unsigned int percentile_precision; /* digits after decimal for percentiles */ fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN]; char *read_iolog_file; char *write_iolog_file; char *bw_log_file; char *lat_log_file; char *iops_log_file; char *replay_redirect; /* * Pre-run and post-run shell */ char *exec_prerun; char *exec_postrun; unsigned int rate[DDIR_RWDIR_CNT]; unsigned int ratemin[DDIR_RWDIR_CNT]; unsigned int ratecycle; unsigned int rate_iops[DDIR_RWDIR_CNT]; unsigned int rate_iops_min[DDIR_RWDIR_CNT]; char *ioscheduler; /* * I/O Error handling */ enum error_type continue_on_error; /* * Benchmark profile type */ char *profile; /* * blkio cgroup support */ char *cgroup; unsigned int cgroup_weight; unsigned int cgroup_nodelete; unsigned int uid; unsigned int gid; int flow_id; int flow; int flow_watermark; unsigned int flow_sleep; unsigned long long offset_increment; unsigned long long number_ios; unsigned int sync_file_range; }; #define FIO_TOP_STR_MAX 256 struct thread_options_pack { uint8_t description[FIO_TOP_STR_MAX]; uint8_t name[FIO_TOP_STR_MAX]; uint8_t directory[FIO_TOP_STR_MAX]; uint8_t filename[FIO_TOP_STR_MAX]; uint8_t filename_format[FIO_TOP_STR_MAX]; uint8_t opendir[FIO_TOP_STR_MAX]; uint8_t ioengine[FIO_TOP_STR_MAX]; uint8_t mmapfile[FIO_TOP_STR_MAX]; uint32_t td_ddir; uint32_t rw_seq; uint32_t kb_base; uint32_t unit_base; uint32_t ddir_seq_nr; uint64_t ddir_seq_add; uint32_t iodepth; uint32_t iodepth_low; uint32_t iodepth_batch; uint32_t iodepth_batch_complete; uint64_t size; uint32_t size_percent; uint32_t fill_device; uint64_t file_size_low; uint64_t file_size_high; uint64_t start_offset; uint32_t bs[DDIR_RWDIR_CNT]; uint32_t ba[DDIR_RWDIR_CNT]; uint32_t min_bs[DDIR_RWDIR_CNT]; uint32_t max_bs[DDIR_RWDIR_CNT]; struct bssplit bssplit[DDIR_RWDIR_CNT][BSSPLIT_MAX]; uint32_t bssplit_nr[DDIR_RWDIR_CNT]; uint32_t ignore_error[ERROR_TYPE_CNT][ERROR_STR_MAX]; uint32_t ignore_error_nr[ERROR_TYPE_CNT]; uint32_t error_dump; uint32_t nr_files; uint32_t open_files; uint32_t file_lock_mode; uint32_t odirect; uint32_t invalidate_cache; uint32_t create_serialize; uint32_t create_fsync; uint32_t create_on_open; uint32_t create_only; uint32_t end_fsync; uint32_t pre_read; uint32_t sync_io; uint32_t verify; uint32_t do_verify; uint32_t verifysort; uint32_t verifysort_nr; uint32_t verify_interval; uint32_t verify_offset; uint8_t verify_pattern[MAX_PATTERN_SIZE]; uint32_t verify_pattern_bytes; uint32_t verify_fatal; uint32_t verify_dump; uint32_t verify_async; uint64_t verify_backlog; uint32_t verify_batch; uint32_t experimental_verify; uint32_t use_thread; uint32_t unlink; uint32_t do_disk_util; uint32_t override_sync; uint32_t rand_repeatable; uint32_t use_os_rand; uint32_t log_avg_msec; uint32_t norandommap; uint32_t softrandommap; uint32_t bs_unaligned; uint32_t fsync_on_close; uint32_t bs_is_seq_rand; uint32_t random_distribution; fio_fp64_t zipf_theta; fio_fp64_t pareto_h; uint32_t random_generator; uint32_t perc_rand[DDIR_RWDIR_CNT]; uint32_t hugepage_size; uint32_t rw_min_bs; uint32_t thinktime; uint32_t thinktime_spin; uint32_t thinktime_blocks; uint32_t fsync_blocks; uint32_t fdatasync_blocks; uint32_t barrier_blocks; uint64_t start_delay; uint64_t timeout; uint64_t ramp_time; uint32_t overwrite; uint32_t bw_avg_time; uint32_t iops_avg_time; uint32_t loops; uint64_t zone_range; uint64_t zone_size; uint64_t zone_skip; uint64_t lockmem; uint32_t mem_type; uint32_t mem_align; uint32_t max_latency; uint32_t stonewall; uint32_t new_group; uint32_t numjobs; uint8_t cpumask[FIO_TOP_STR_MAX]; uint32_t cpumask_set; uint8_t verify_cpumask[FIO_TOP_STR_MAX]; uint32_t verify_cpumask_set; uint32_t iolog; uint32_t rwmixcycle; uint32_t rwmix[2]; uint32_t nice; uint32_t ioprio; uint32_t ioprio_class; uint32_t file_service_type; uint32_t group_reporting; uint32_t fadvise_hint; uint32_t fallocate_mode; uint32_t zero_buffers; uint32_t refill_buffers; uint32_t scramble_buffers; unsigned int compress_percentage; unsigned int compress_chunk; uint32_t time_based; uint32_t disable_lat; uint32_t disable_clat; uint32_t disable_slat; uint32_t disable_bw; uint32_t unified_rw_rep; uint32_t gtod_reduce; uint32_t gtod_cpu; uint32_t gtod_offload; uint32_t clocksource; uint32_t no_stall; uint32_t trim_percentage; uint32_t trim_batch; uint32_t trim_zero; uint64_t trim_backlog; uint32_t clat_percentiles; uint32_t percentile_precision; fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN]; uint8_t read_iolog_file[FIO_TOP_STR_MAX]; uint8_t write_iolog_file[FIO_TOP_STR_MAX]; uint8_t bw_log_file[FIO_TOP_STR_MAX]; uint8_t lat_log_file[FIO_TOP_STR_MAX]; uint8_t iops_log_file[FIO_TOP_STR_MAX]; uint8_t replay_redirect[FIO_TOP_STR_MAX]; /* * Pre-run and post-run shell */ uint8_t exec_prerun[FIO_TOP_STR_MAX]; uint8_t exec_postrun[FIO_TOP_STR_MAX]; uint32_t rate[DDIR_RWDIR_CNT]; uint32_t ratemin[DDIR_RWDIR_CNT]; uint32_t ratecycle; uint32_t rate_iops[DDIR_RWDIR_CNT]; uint32_t rate_iops_min[DDIR_RWDIR_CNT]; uint8_t ioscheduler[FIO_TOP_STR_MAX]; /* * I/O Error handling */ uint32_t continue_on_error; /* * Benchmark profile type */ uint8_t profile[FIO_TOP_STR_MAX]; /* * blkio cgroup support */ uint8_t cgroup[FIO_TOP_STR_MAX]; uint32_t cgroup_weight; uint32_t cgroup_nodelete; uint32_t uid; uint32_t gid; int32_t flow_id; int32_t flow; int32_t flow_watermark; uint32_t flow_sleep; uint64_t offset_increment; uint64_t number_ios; uint32_t sync_file_range; } __attribute__((packed)); extern void convert_thread_options_to_cpu(struct thread_options *o, struct thread_options_pack *top); extern void convert_thread_options_to_net(struct thread_options_pack *top, struct thread_options *); extern int fio_test_cconv(struct thread_options *); extern void options_default_fill(struct thread_options *o); #endif fio-2.1.3/tickmarks.c000066400000000000000000000062461222032232000144220ustar00rootroot00000000000000#include #include #include #include /* * adapted from Paul Heckbert's algorithm on p 657-659 of * Andrew S. Glassner's book, "Graphics Gems" * ISBN 0-12-286166-3 * */ #include "tickmarks.h" #define MAX(a, b) (((a) < (b)) ? (b) : (a)) static double nicenum(double x, int round) { int exp; /* exponent of x */ double f; /* fractional part of x */ exp = floor(log10(x)); f = x / pow(10.0, exp); if (round) { if (f < 1.5) return 1.0 * pow(10.0, exp); if (f < 3.0) return 2.0 * pow(10.0, exp); if (f < 7.0) return 5.0 * pow(10.0, exp); return 10.0 * pow(10.0, exp); } if (f <= 1.0) return 1.0 * pow(10.0, exp); if (f <= 2.0) return 2.0 * pow(10.0, exp); if (f <= 5.0) return 5.0 * pow(10.0, exp); return 10.0 * pow(10.0, exp); } static void shorten(struct tickmark *tm, int nticks, int *power_of_ten, int use_KMG_symbols, int base_offset) { const char shorten_chr[] = { 0, 'K', 'M', 'G', 'P', 'E', 0 }; int i, l, minshorten, shorten_idx = 0; char *str; minshorten = 100; for (i = 0; i < nticks; i++) { str = tm[i].string; l = strlen(str); if (strcmp(str, "0") == 0) continue; if (l > 9 && strcmp(&str[l - 9], "000000000") == 0) { *power_of_ten = 9; shorten_idx = 3; } else if (6 < minshorten && l > 6 && strcmp(&str[l - 6], "000000") == 0) { *power_of_ten = 6; shorten_idx = 2; } else if (l > 3 && strcmp(&str[l - 3], "000") == 0) { *power_of_ten = 3; shorten_idx = 1; } else { *power_of_ten = 0; } if (*power_of_ten < minshorten) minshorten = *power_of_ten; } if (minshorten == 0) return; if (!use_KMG_symbols) shorten_idx = 0; else if (base_offset) shorten_idx += base_offset; for (i = 0; i < nticks; i++) { str = tm[i].string; l = strlen(str); str[l - minshorten] = shorten_chr[shorten_idx]; if (shorten_idx) str[l - minshorten + 1] = '\0'; } } int calc_tickmarks(double min, double max, int nticks, struct tickmark **tm, int *power_of_ten, int use_KMG_symbols, int base_offset) { char str[100]; int nfrac; double d; /* tick mark spacing */ double graphmin, graphmax; /* graph range min and max */ double range, x; int count, i; /* we expect min != max */ range = nicenum(max - min, 0); d = nicenum(range / (nticks - 1), 1); graphmin = floor(min / d) * d; graphmax = ceil(max / d) * d; nfrac = MAX(-floor(log10(d)), 0); snprintf(str, sizeof(str)-1, "%%.%df", nfrac); count = ((graphmax + 0.5 * d) - graphmin) / d + 1; *tm = malloc(sizeof(**tm) * count); i = 0; for (x = graphmin; x < graphmax + 0.5 * d; x += d) { (*tm)[i].value = x; sprintf((*tm)[i].string, str, x); i++; } shorten(*tm, i, power_of_ten, use_KMG_symbols, base_offset); return i; } #if 0 static void test_range(double x, double y) { int nticks, i; struct tickmark *tm = NULL; printf("Testing range %g - %g\n", x, y); nticks = calc_tickmarks(x, y, 10, &tm); for (i = 0; i < nticks; i++) printf(" (%s) %g\n", tm[i].string, tm[i].value); printf("\n\n"); free(tm); } int main(int argc, char *argv[]) { test_range(0.0005, 0.008); test_range(0.5, 0.8); test_range(5.5, 8.8); test_range(50.5, 80.8); test_range(-20, 20.8); test_range(-30, 700.8); } #endif fio-2.1.3/tickmarks.h000066400000000000000000000003571222032232000144240ustar00rootroot00000000000000#ifndef TICKMARKS_H #define TICKMARKS_H struct tickmark { double value; char string[20]; }; int calc_tickmarks(double min, double max, int nticks, struct tickmark **tm, int *power_of_ten, int use_KMG_symbols, int base_off); #endif fio-2.1.3/time.c000066400000000000000000000035671222032232000133730ustar00rootroot00000000000000#include #include #include "fio.h" static struct timeval genesis; static unsigned long ns_granularity; /* * busy looping version for the last few usec */ void usec_spin(unsigned int usec) { struct timeval start; fio_gettime(&start, NULL); while (utime_since_now(&start) < usec) nop; } void usec_sleep(struct thread_data *td, unsigned long usec) { struct timespec req; struct timeval tv; do { unsigned long ts = usec; if (usec < ns_granularity) { usec_spin(usec); break; } ts = usec - ns_granularity; if (ts >= 1000000) { req.tv_sec = ts / 1000000; ts -= 1000000 * req.tv_sec; } else req.tv_sec = 0; req.tv_nsec = ts * 1000; fio_gettime(&tv, NULL); if (nanosleep(&req, NULL) < 0) break; ts = utime_since_now(&tv); if (ts >= usec) break; usec -= ts; } while (!td->terminate); } uint64_t mtime_since_genesis(void) { return mtime_since_now(&genesis); } int in_ramp_time(struct thread_data *td) { return td->o.ramp_time && !td->ramp_time_over; } int ramp_time_over(struct thread_data *td) { struct timeval tv; if (!td->o.ramp_time || td->ramp_time_over) return 1; fio_gettime(&tv, NULL); if (mtime_since(&td->epoch, &tv) >= td->o.ramp_time * 1000) { td->ramp_time_over = 1; reset_all_stats(td); td_set_runstate(td, TD_RAMP); return 1; } return 0; } void fio_time_init(void) { int i; fio_clock_init(); /* * Check the granularity of the nanosleep function */ for (i = 0; i < 10; i++) { struct timeval tv; struct timespec ts; unsigned long elapsed; fio_gettime(&tv, NULL); ts.tv_sec = 0; ts.tv_nsec = 1000; nanosleep(&ts, NULL); elapsed = utime_since_now(&tv); if (elapsed > ns_granularity) ns_granularity = elapsed; } } void set_genesis_time(void) { fio_gettime(&genesis, NULL); } void fill_start_time(struct timeval *t) { memcpy(t, &genesis, sizeof(genesis)); } fio-2.1.3/time.h000066400000000000000000000012521222032232000133650ustar00rootroot00000000000000#ifndef FIO_TIME_H #define FIO_TIME_H extern uint64_t utime_since(struct timeval *, struct timeval *); extern uint64_t utime_since_now(struct timeval *); extern uint64_t mtime_since(struct timeval *, struct timeval *); extern uint64_t mtime_since_now(struct timeval *); extern uint64_t time_since_now(struct timeval *); extern uint64_t mtime_since_genesis(void); extern void usec_spin(unsigned int); extern void usec_sleep(struct thread_data *, unsigned long); extern void fill_start_time(struct timeval *); extern void set_genesis_time(void); extern int ramp_time_over(struct thread_data *); extern int in_ramp_time(struct thread_data *); extern void fio_time_init(void); #endif fio-2.1.3/tools/000077500000000000000000000000001222032232000134165ustar00rootroot00000000000000fio-2.1.3/tools/.gitignore000066400000000000000000000000031222032232000153770ustar00rootroot00000000000000*~ fio-2.1.3/tools/fio_generate_plots000077500000000000000000000076321222032232000172240ustar00rootroot00000000000000#!/bin/sh # # This script is an almost total rewrite by Louwrentius # of the original fio_generate_plots script provided as part of the FIO storage # benchmark utiliy. I only retained how GNUplot is used to generate graphs, as # that is something I know nothing about. # # The script uses the files generated by FIO to create nice graphs in the # SVG format. This output format is supported by most modern browsers and # allows resolution independant graphs to be generated. # # This script supports GNUPLOT 4.4 and higher. # # Version 1.0 @ 20121231 # # # if [ -z "$1" ]; then echo "Usage: fio_generate_plots subtitle [xres yres]" exit 1 fi GNUPLOT=$(which gnuplot) if [ ! -x "$GNUPLOT" ] then echo You need gnuplot installed to generate graphs exit 1 fi TITLE="$1" # set resolution if [ ! -z "$2" ] && [ ! -z "$3" ] then XRES="$2" YRES="$3" else XRES=1280 YRES=768 fi if [ -z "$SAMPLE_DURATION" ] then SAMPLE_DURATION="*" fi DEFAULT_GRID_LINE_TYPE=3 DEFAULT_LINE_WIDTH=2 DEFAULT_LINE_COLORS=" set object 1 rectangle from screen 0,0 to screen 1,1 fillcolor rgb\"#ffffff\" behind set style line 1 lc rgb \"#E41A1C\" lw $DEFAULT_LINE_WIDTH lt 1; set style line 2 lc rgb \"#377EB8\" lw $DEFAULT_LINE_WIDTH lt 1; set style line 3 lc rgb \"#4DAF4A\" lw $DEFAULT_LINE_WIDTH lt 1; set style line 4 lc rgb \"#984EA3\" lw $DEFAULT_LINE_WIDTH lt 1; set style line 5 lc rgb \"#FF7F00\" lw $DEFAULT_LINE_WIDTH lt 1; set style line 6 lc rgb \"#DADA33\" lw $DEFAULT_LINE_WIDTH lt 1; set style line 7 lc rgb \"#A65628\" lw $DEFAULT_LINE_WIDTH lt 1; set style line 20 lc rgb \"#000000\" lt $DEFAULT_GRID_LINE_TYPE lw $DEFAULT_LINE_WIDTH; " DEFAULT_TERMINAL="set terminal svg enhanced dashed size $XRES,$YRES dynamic" DEFAULT_TITLE_FONT="\"Helvetica,28\"" DEFAULT_AXIS_FONT="\"Helvetica,14\"" DEFAULT_AXIS_LABEL_FONT="\"Helvetica,16\"" DEFAULT_XLABEL="set xlabel \"Time (sec)\" font $DEFAULT_AXIS_LABEL_FONT" DEFAULT_XTIC="set xtics font $DEFAULT_AXIS_FONT" DEFAULT_YTIC="set ytics font $DEFAULT_AXIS_FONT" DEFAULT_MXTIC="set mxtics 0" DEFAULT_MYTIC="set mytics 2" DEFAULT_XRANGE="set xrange [0:$SAMPLE_DURATION]" DEFAULT_YRANGE="set yrange [0:*]" DEFAULT_GRID="set grid ls 20" DEFAULT_KEY="set key outside bottom center ; set key box enhanced spacing 2.0 samplen 3 horizontal width 4 height 1.2 " DEFAULT_SOURCE="set label 30 \"Data source: http://example.com\" font $DEFAULT_AXIS_FONT tc rgb \"#00000f\" at screen 0.976,0.175 right" DEFAULT_OPTS="$DEFAULT_LINE_COLORS ; $DEFAULT_GRID_LINE ; $DEFAULT_GRID ; $DEFAULT_GRID_MINOR ; $DEFAULT_XLABEL ; $DEFAULT_XRANGE ; $DEFAULT_YRANGE ; $DEFAULT_XTIC ; $DEFAULT_YTIC ; $DEFAULT_MXTIC ; $DEFAULT_MYTIC ; $DEFAULT_KEY ; $DEFAULT_TERMINAL ; $DEFAULT_SOURCE" plot () { if [ -z "$TITLE" ] then PLOT_TITLE=" set title \"$1\" font $DEFAULT_TITLE_FONT" else PLOT_TITLE=" set title \"$TITLE\\\n\\\n{/*0.6 "$1"}\" font $DEFAULT_TITLE_FONT" fi FILETYPE="$2" YAXIS="set ylabel \"$3\" font $DEFAULT_AXIS_LABEL_FONT" SCALE=$4 echo "Title: $PLOT_TITLE" echo "File type: $FILETYPE" echo "yaxis: $YAXIS" i=0 for x in *_"$FILETYPE".log do i=$((i+1)) PT=$(echo $x | sed s/_"$FILETYPE".log//g) if [ ! -z "$PLOT_LINE" ] then PLOT_LINE=$PLOT_LINE", " fi DEPTH=$(echo $PT | cut -d "-" -f 4) PLOT_LINE=$PLOT_LINE"'$x' using (\$1/1000):(\$2/$SCALE) title \"Queue depth $DEPTH\" with lines ls $i" done OUTPUT="set output \"$TITLE-$FILETYPE.svg\" " echo " $PLOT_TITLE ; $YAXIS ; $DEFAULT_OPTS ; show style lines ; $OUTPUT ; plot " $PLOT_LINE | $GNUPLOT - unset PLOT_LINE } # # plot # plot "I/O Latency" lat "Time (msec)" 1000 plot "I/O Operations Per Second" iops "IOPS" 1 plot "I/O Submission Latency" slat "Time (μsec)" 1 plot "I/O Completion Latency" clat "Time (msec)" 1000 plot "I/O Bandwidth" bw "Throughput (KB/s)" 1 fio-2.1.3/tools/fio_generate_plots.1000066400000000000000000000033361222032232000173550ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH FIO_GENERATE_PLOTS 1 "May 19, 2009" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME fio_generate_plots \- Generate plots for Flexible I/O Tester .SH SYNOPSIS .B fio_generate_plots .RI " title" .br .SH DESCRIPTION This manual page documents briefly the .B fio_generate_plots command. This manual page was written for the Debian distribution because the original program does not have a manual page. .PP .\" TeX users may be more comfortable with the \fB\fP and .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. \fBfio_generate_plots\fP is a shell script that uses gnuplot to generate plots from fio run with \-\-latency-log (\-l) and/or \-\-bandwidth-log (\-w). It expects the log files that fio generated in the current directory. .SH OPTIONS The script takes the title of the plot as only argument. It does not offer any additional options. .SH AUTHOR fio_generate_plots was written by Jens Axboe , now Jens Axboe . .PP This manual page was written by Martin Steigerwald , for the Debian project (but may be used by others). fio-2.1.3/tools/genfio000077500000000000000000000205121222032232000146130ustar00rootroot00000000000000#!/bin/bash # # Copyright (C) 2013 eNovance SAS # Author: Erwan Velu # # The license below covers all files distributed with fio unless otherwise # noted in the file itself. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA BLK_SIZE= BLOCK_SIZE=4k SEQ=-1 TEMPLATE=/tmp/template.fio OUTFILE= DISKS= PRINTABLE_DISKS= RUNTIME=300 ETA=0 MODES="write,randwrite,read,randread" SHORT_HOSTNAME= CACHED_IO="FALSE" PREFIX="" PREFIX_FILENAME="" IODEPTH=1 show_help() { PROG=$(basename $0) echo "usage of $PROG:" cat << EOF -h : Show this help & exit -c : Enable cached-based IOs Disabled by default -a : Run sequential test then parallel one Disabled by default -s : Run sequential test (default value) one test after another then one disk after another Disabled by default -p : Run parallel test one test after anoter but all disks at the same time Enabled by default -D iodepth : Run with the specified iodepth Default is $IODEPTH -d disk1[,disk2,disk3,..] : Run the tests on the selected disks Separated each disk with a comma -r seconds : Time in seconds per benchmark 0 means till the end of the device Default is $RUNTIME seconds -b blocksize[,blocksize1, ...] : The blocksizes to test under fio format (4k, 1m, ...) Separated each blocksize with a comma Default is $BLOCK_SIZE -m mode1,[mode2,mode3, ...] : Define the fio IO profile to use like read, write, randread, randwrite Default is "$MODES" -x prefix : Add a prefix to the fio filename Useful to let a context associated with the file If the prefix features a / (slash), prefix will be considered as a directory -A cmd_to_run : System command to run after each job (exec_postrun in fio) -B cmd_to_run : System command to run before each job (exec_prerun in fio) Example: $PROG -d /dev/sdb,/dev/sdc,/dev/sdd,/dev/sde -a -b 4k,128k,1m -r 100 -a -x dellr720-day2/ Will generate an fio file that will run - a sequential bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 4k with write,randwrite,read,randread tests ETA ~ 4 tests * 4 disks * 100 seconds - a sequential bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 128k with write,randwrite,read,randread tests ETA ~ 4 tests * 4 disks * 100 seconds - a sequential bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 1m with write,randwrite,read,randread tests ETA ~ 4 tests * 4 disks * 100 seconds - a parallel bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 4k with write,randwrite,read,randread tests ETA ~ 4 tests * 100 seconds - a parallel bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 128k with write,randwrite,read,randread tests ETA ~ 4 tests * 100 seconds - a parallel bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 1m with write,randwrite,read,randread tests ETA ~ 4 tests * 100 seconds Generating dellr720-day2/localhost-4k,128k,1m-all-write,randwrite,read,randread-sdb,sdc,sdd,sde.fio Estimated Time = 6000 seconds : 1 hour 40 minutes EOF } finish_template() { echo "iodepth=$IODEPTH" >> $TEMPLATE if [ "$RUNTIME" != "0" ]; then echo "runtime=$RUNTIME" >> $TEMPLATE echo "time_based" >> $TEMPLATE fi if [ "$CACHED_IO" = "FALSE" ]; then echo "direct=1" >> $TEMPLATE fi } diskname_to_printable() { COUNT=0 for disk in $(echo $@ | tr "," " "); do R=$(basename $disk | sed 's|/|_|g') COUNT=$(($COUNT + 1)) if [ $COUNT -eq 1 ]; then P="$R" else P="$P,$R" fi done echo $P } gen_template() { cat >$TEMPLATE << EOF [global] ioengine=libaio invalidate=1 ramp_time=5 EOF } gen_seq_suite() { TYPE=$1 disk=$2 PRINTABLE_DISK=$(diskname_to_printable $disk) cat >> $OUTFILE << EOF [$TYPE-$PRINTABLE_DISK-$BLK_SIZE-seq] stonewall bs=$BLK_SIZE filename=$disk rw=$TYPE write_bw_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-seq.results write_iops_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-seq.results EOF ETA=$(($ETA + $RUNTIME)) } gen_seq_fio() { for disk in $(echo $DISKS | tr "," " "); do for mode in $(echo $MODES | tr "," " "); do gen_seq_suite "$mode" "$disk" done done } gen_para_suite() { TYPE=$1 NEED_WALL=$2 D=0 for disk in $(echo $DISKS | tr "," " "); do PRINTABLE_DISK=$(diskname_to_printable $disk) cat >> $OUTFILE << EOF [$TYPE-$PRINTABLE_DISK-$BLK_SIZE-para] bs=$BLK_SIZE EOF if [ "$D" = 0 ]; then echo "stonewall" >> $OUTFILE D=1 fi cat >> $OUTFILE << EOF filename=$disk rw=$TYPE write_bw_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-para.results write_iops_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-para.results EOF done ETA=$(($ETA + $RUNTIME)) echo >> $OUTFILE } gen_para_fio() { for mode in $(echo $MODES | tr "," " "); do gen_para_suite "$mode" done } gen_fio() { case $SEQ in 2) gen_seq_fio gen_para_fio ;; 1) gen_seq_fio ;; 0) gen_para_fio ;; esac } parse_cmdline() { while getopts "hacpsd:b:r:m:x:D:A:B:" opt; do case $opt in h) show_help exit 0 ;; b) BLOCK_SIZE=$OPTARG ;; c) CACHED_IO="TRUE" ;; s) if [ "$SEQ" = "-1" ]; then SEQ=1 fi ;; x) PREFIX=$OPTARG echo "$PREFIX" | grep -q "/" if [ "$?" -eq 0 ]; then mkdir -p $PREFIX # No need to keep the prefix for the log files # we do have a directory for that PREFIX_FILENAME="" else # We need to keep the prefix for the log files PREFIX_FILENAME=$PREFIX fi ;; r) RUNTIME=$OPTARG ;; p) if [ "$SEQ" = "-1" ]; then SEQ=0 fi ;; m) MODES=$OPTARG; ;; d) DISKS=$OPTARG PRINTABLE_DISKS=$(diskname_to_printable "$DISKS") ;; D) IODEPTH=$OPTARG ;; a) SEQ=2 ;; B) echo "exec_prerun=$OPTARG" >> $TEMPLATE ;; A) echo "exec_postrun=$OPTARG" >> $TEMPLATE ;; \?) echo "Invalid option: -$OPTARG" >&2 ;; esac done if [ "$SEQ" = "-1" ]; then SEQ=0 fi SHORT_HOSTNAME=$(hostname -s) case $SEQ in 2) OUTFILE=${PREFIX}$SHORT_HOSTNAME-$BLOCK_SIZE-all-$MODES-$PRINTABLE_DISKS.fio ;; 1) OUTFILE=${PREFIX}$SHORT_HOSTNAME-$BLOCK_SIZE-sequential-$MODES-$PRINTABLE_DISKS.fio ;; 0) OUTFILE=${PREFIX}$SHORT_HOSTNAME-$BLOCK_SIZE-parallel-$MODES-$PRINTABLE_DISKS.fio ;; esac if [ -z "$DISKS" ]; then echo "Missing DISKS !" echo "Please read the help !" show_help exit 1 fi } check_mode_order() { FOUND_WRITE="NO" CAUSE="You are reading data before writing them " # If no write occurs, let's show a different message echo $MODES | grep -q "write" if [ "$?" -ne 0 ]; then CAUSE="You are reading data while never wrote them before" fi for mode in $(echo $MODES | tr "," " "); do echo $mode | grep -q write if [ "$?" -eq 0 ]; then FOUND_WRITE="YES" fi echo $mode | grep -q "read" if [ "$?" -eq 0 ]; then if [ "$FOUND_WRITE" = "NO" ]; then echo "###############################################################" echo "# Warning : $CAUSE#" echo "# On some storage devices, this could lead to invalid results #" echo "# #" echo "# Press Ctrl-C to adjust pattern order if you have doubts #" echo "# Or Wait 5 seconds before the file will be created #" echo "###############################################################" sleep 5 # No need to try showing the message more than one time return fi fi done } ########## MAIN gen_template parse_cmdline "$@" finish_template check_mode_order echo "Generating $OUTFILE" cp -f $TEMPLATE $OUTFILE echo >> $OUTFILE for BLK_SIZE in $(echo $BLOCK_SIZE | tr "," " "); do gen_fio done ETA_H=$(($ETA / 3600)) ETA_M=$((($ETA - ($ETA_H*3600)) / 60)) if [ "$ETA" = "0" ]; then echo "Cannot estimate ETA as RUNTIME=0" else echo "Estimated Time = $ETA seconds : $ETA_H hour $ETA_M minutes" fi fio-2.1.3/tools/plot/000077500000000000000000000000001222032232000143745ustar00rootroot00000000000000fio-2.1.3/tools/plot/fio2gnuplot000077500000000000000000000514101222032232000165730ustar00rootroot00000000000000#!/usr/bin/python # # Copyright (C) 2013 eNovance SAS # Author: Erwan Velu # # The license below covers all files distributed with fio unless otherwise # noted in the file itself. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os import fnmatch import sys import getopt import re import math import shutil def find_file(path, pattern): fio_data_file=[] # For all the local files for file in os.listdir(path): # If the file math the regexp if fnmatch.fnmatch(file, pattern): # Let's consider this file fio_data_file.append(file) return fio_data_file def generate_gnuplot_script(fio_data_file,title,gnuplot_output_filename,gnuplot_output_dir,mode,disk_perf,gpm_dir): if verbose: print "Generating rendering scripts" filename=gnuplot_output_dir+'mygraph' temporary_files.append(filename) f=open(filename,'w') # Plotting 3D or comparing graphs doesn't have a meaning unless if there is at least 2 traces if len(fio_data_file) > 1: f.write("call \'%s/graph3D.gpm\' \'%s' \'%s\' \'\' \'%s\' \'%s\'\n" % (gpm_dir,title,gnuplot_output_filename,gnuplot_output_filename,mode)) # Setting up the compare files that will be plot later compare=open(gnuplot_output_dir + 'compare.gnuplot','w') compare.write(''' set title '%s' set terminal png size 1280,1024 set ytics axis out auto set key top left reverse set xlabel "Time (Seconds)" set ylabel '%s' set yrange [0:] set style line 1 lt 1 lw 3 pt 3 linecolor rgb "green" '''% (title,mode)) compare.close() #Copying the common file for all kind of graph (raw/smooth/trend) compare_raw_filename="compare-%s-2Draw" % (gnuplot_output_filename) compare_smooth_filename="compare-%s-2Dsmooth" % (gnuplot_output_filename) compare_trend_filename="compare-%s-2Dtrend" % (gnuplot_output_filename) shutil.copy(gnuplot_output_dir+'compare.gnuplot',gnuplot_output_dir+compare_raw_filename+".gnuplot") shutil.copy(gnuplot_output_dir+'compare.gnuplot',gnuplot_output_dir+compare_smooth_filename+".gnuplot") shutil.copy(gnuplot_output_dir+'compare.gnuplot',gnuplot_output_dir+compare_trend_filename+".gnuplot") temporary_files.append(gnuplot_output_dir+compare_raw_filename+".gnuplot") temporary_files.append(gnuplot_output_dir+compare_smooth_filename+".gnuplot") temporary_files.append(gnuplot_output_dir+compare_trend_filename+".gnuplot") #Setting up a different output filename for each kind of graph compare_raw=open(gnuplot_output_dir+compare_raw_filename + ".gnuplot",'a') compare_raw.write("set output '%s.png'\n" % compare_raw_filename) compare_smooth=open(gnuplot_output_dir+compare_smooth_filename+".gnuplot",'a') compare_smooth.write("set output '%s.png'\n" % compare_smooth_filename) compare_trend=open(gnuplot_output_dir+compare_trend_filename+".gnuplot",'a') compare_trend.write("set output '%s.png'\n" % compare_trend_filename) # Let's plot the average value for all the traces global_disk_perf = sum(disk_perf, []) global_avg = average(global_disk_perf) compare_raw.write("plot %s w l ls 1 ti 'Global average value (%.2f)'" % (global_avg,global_avg)); compare_smooth.write("plot %s w l ls 1 ti 'Global average value (%.2f)'" % (global_avg,global_avg)); compare_trend.write("plot %s w l ls 1 ti 'Global average value (%.2f)'" % (global_avg,global_avg)); pos=0 # Let's create a temporary file for each selected fio file for file in fio_data_file: tmp_filename = "gnuplot_temp_file.%d" % pos # Plotting comparing graphs doesn't have a meaning unless if there is at least 2 traces if len(fio_data_file) > 1: # Adding the plot instruction for each kind of comparing graphs compare_raw.write(",\\\n'%s' using 2:3 with linespoints title '%s'" % (tmp_filename,fio_data_file[pos])) compare_smooth.write(",\\\n'%s' using 2:3 smooth csplines title '%s'" % (tmp_filename,fio_data_file[pos])) compare_trend.write(",\\\n'%s' using 2:3 smooth bezier title '%s'" % (tmp_filename,fio_data_file[pos])) png_file=file.replace('.log','') raw_filename = "%s-2Draw" % (png_file) smooth_filename = "%s-2Dsmooth" % (png_file) trend_filename = "%s-2Dtrend" % (png_file) avg = average(disk_perf[pos]) f.write("call \'%s/graph2D.gpm\' \'%s' \'%s\' \'%s\' \'%s\' \'%s\' \'%s\' \'%s\' \'%f\'\n" % (gpm_dir,title,tmp_filename,fio_data_file[pos],raw_filename,mode,smooth_filename,trend_filename,avg)) pos = pos +1 # Plotting comparing graphs doesn't have a meaning unless if there is at least 2 traces if len(fio_data_file) > 1: os.remove(gnuplot_output_dir+"compare.gnuplot") compare_raw.close() compare_smooth.close() compare_trend.close() f.close() def generate_gnuplot_math_script(title,gnuplot_output_filename,mode,average,gnuplot_output_dir,gpm_dir): filename=gnuplot_output_dir+'mymath'; temporary_files.append(filename) f=open(filename,'a') f.write("call \'%s/math.gpm\' \'%s' \'%s\' \'\' \'%s\' \'%s\' %s\n" % (gpm_dir,title,gnuplot_output_filename,gnuplot_output_filename,mode,average)) f.close() def compute_aggregated_file(fio_data_file, gnuplot_output_filename, gnuplot_output_dir): if verbose: print "Processing data file 2/2" temp_files=[] pos=0 # Let's create a temporary file for each selected fio file for file in fio_data_file: tmp_filename = "%sgnuplot_temp_file.%d" % (gnuplot_output_dir, pos) temp_files.append(open(tmp_filename,'r')) pos = pos +1 f = open(gnuplot_output_dir+gnuplot_output_filename, "w") temporary_files.append(gnuplot_output_dir+gnuplot_output_filename) index=0 # Let's add some information for tempfile in temp_files: f.write("# Disk%d was coming from %s\n" % (index,fio_data_file[index])) f.write(tempfile.read()) f.write("\n") tempfile.close() index = index + 1 f.close() def average(s): return sum(s) * 1.0 / len(s) def compute_temp_file(fio_data_file,disk_perf,gnuplot_output_dir, min_time, max_time): end_time=max_time if end_time == -1: end_time="infinite" if verbose: print "Processing data file 1/2 with %s(float(min_time)*1000)) and ((int(time) < (int(max_time)*1000)) or max_time==-1)): disk_perf[index].append(int(perf)) perfs.append("%d %s %s"% (index, time, perf)) # If we reach this point, it means that all the traces are coherent for p in enumerate(perfs): index, perf_time,perf = p[1].split() temp_outfile[int(index)].write("%s %.2f %s\n" % (index, float(float(perf_time)/1000), perf)) for file in files: file.close() for file in temp_outfile: file.close() return blk_size def compute_math(fio_data_file, title,gnuplot_output_filename,gnuplot_output_dir,mode,disk_perf,gpm_dir): if verbose: print "Computing Maths" global_min=[] global_max=[] average_file=open(gnuplot_output_dir+gnuplot_output_filename+'.average', 'w') min_file=open(gnuplot_output_dir+gnuplot_output_filename+'.min', 'w') max_file=open(gnuplot_output_dir+gnuplot_output_filename+'.max', 'w') stddev_file=open(gnuplot_output_dir+gnuplot_output_filename+'.stddev', 'w') global_file=open(gnuplot_output_dir+gnuplot_output_filename+'.global','w') temporary_files.append(gnuplot_output_dir+gnuplot_output_filename+'.average') temporary_files.append(gnuplot_output_dir+gnuplot_output_filename+'.min') temporary_files.append(gnuplot_output_dir+gnuplot_output_filename+'.max') temporary_files.append(gnuplot_output_dir+gnuplot_output_filename+'.stddev') temporary_files.append(gnuplot_output_dir+gnuplot_output_filename+'.global') min_file.write('DiskName %s\n' % mode) max_file.write('DiskName %s\n'% mode) average_file.write('DiskName %s\n'% mode) stddev_file.write('DiskName %s\n'% mode ) for disk in xrange(len(fio_data_file)): # print disk_perf[disk] min_file.write("# Disk%d was coming from %s\n" % (disk,fio_data_file[disk])) max_file.write("# Disk%d was coming from %s\n" % (disk,fio_data_file[disk])) average_file.write("# Disk%d was coming from %s\n" % (disk,fio_data_file[disk])) stddev_file.write("# Disk%d was coming from %s\n" % (disk,fio_data_file[disk])) avg = average(disk_perf[disk]) variance = map(lambda x: (x - avg)**2, disk_perf[disk]) standard_deviation = math.sqrt(average(variance)) # print "Disk%d [ min=%.2f max=%.2f avg=%.2f stddev=%.2f \n" % (disk,min(disk_perf[disk]),max(disk_perf[disk]),avg, standard_deviation) average_file.write('%d %d\n' % (disk, avg)) stddev_file.write('%d %d\n' % (disk, standard_deviation)) local_min=min(disk_perf[disk]) local_max=max(disk_perf[disk]) min_file.write('%d %d\n' % (disk, local_min)) max_file.write('%d %d\n' % (disk, local_max)) global_min.append(int(local_min)) global_max.append(int(local_max)) global_disk_perf = sum(disk_perf, []) avg = average(global_disk_perf) variance = map(lambda x: (x - avg)**2, global_disk_perf) standard_deviation = math.sqrt(average(variance)) global_file.write('min=%.2f\n' % min(global_disk_perf)) global_file.write('max=%.2f\n' % max(global_disk_perf)) global_file.write('avg=%.2f\n' % avg) global_file.write('stddev=%.2f\n' % standard_deviation) global_file.write('values_count=%d\n' % len(global_disk_perf)) global_file.write('disks_count=%d\n' % len(fio_data_file)) #print "Global [ min=%.2f max=%.2f avg=%.2f stddev=%.2f \n" % (min(global_disk_perf),max(global_disk_perf),avg, standard_deviation) average_file.close() min_file.close() max_file.close() stddev_file.close() global_file.close() try: os.remove(gnuplot_output_dir+'mymath') except: True generate_gnuplot_math_script("Average values of "+title,gnuplot_output_filename+'.average',mode,int(avg),gnuplot_output_dir,gpm_dir) generate_gnuplot_math_script("Min values of "+title,gnuplot_output_filename+'.min',mode,average(global_min),gnuplot_output_dir,gpm_dir) generate_gnuplot_math_script("Max values of "+title,gnuplot_output_filename+'.max',mode,average(global_max),gnuplot_output_dir,gpm_dir) generate_gnuplot_math_script("Standard Deviation of "+title,gnuplot_output_filename+'.stddev',mode,int(standard_deviation),gnuplot_output_dir,gpm_dir) def parse_global_files(fio_data_file, global_search): max_result=0 max_file='' for file in fio_data_file: f=open(file) disk_count=0 search_value=-1 # Let's read the complete file while True: try: # We do split the name from the value name,value=f.readline().split("=") except: f.close() break # If we ended the file if not name: # Let's process what we have f.close() break else: # disks_count is not global_search item # As we need it for some computation, let's save it if name=="disks_count": disks_count=int(value) # Let's catch the searched item if global_search in name: search_value=float(value) # Let's process the avg value by estimated the global bandwidth per file # We keep the biggest in memory for reporting if global_search == "avg": if (disks_count > 0) and (search_value != -1): result=disks_count*search_value if (result > max_result): max_result=result max_file=file # Let's print the avg output if global_search == "avg": print "Biggest aggregated value of %s was %2.f in file %s\n" % (global_search, max_result, max_file) else: print "Global search %s is not yet implemented\n" % global_search def render_gnuplot(fio_data_file, gnuplot_output_dir): print "Running gnuplot Rendering" try: # Let's render all the compared files if some if len(fio_data_file) > 1: if verbose: print " |-> Rendering comparing traces" os.system("cd %s; for i in *.gnuplot; do gnuplot $i; done" % gnuplot_output_dir) if verbose: print " |-> Rendering math traces" os.system("cd %s; gnuplot mymath" % gnuplot_output_dir) if verbose: print " |-> Rendering 2D & 3D traces" os.system("cd %s; gnuplot mygraph" % gnuplot_output_dir) name_of_directory="the current" if gnuplot_output_dir != "./": name_of_directory=gnuplot_output_dir print "\nRendering traces are available in %s directory" % name_of_directory global keep_temp_files keep_temp_files=False except: print "Could not run gnuplot on mymath or mygraph !\n" sys.exit(1); def print_help(): print 'fio2gnuplot -ghbiodvk -t -o <outputfile> -p <pattern> -G <type> -m <time> -M <time>' print print '-h --help : Print this help' print '-p <pattern> or --pattern <pattern> : A pattern in regexp to select fio input files' print '-b or --bandwidth : A predefined pattern for selecting *_bw.log files' print '-i or --iops : A predefined pattern for selecting *_iops.log files' print '-g or --gnuplot : Render gnuplot traces before exiting' print '-o or --outputfile <file> : The basename for gnuplot traces' print ' - Basename is set with the pattern if defined' print '-d or --outputdir <dir> : The directory where gnuplot shall render files' print '-t or --title <title> : The title of the gnuplot traces' print ' - Title is set with the block size detected in fio traces' print '-G or --Global <type> : Search for <type> in .global files match by a pattern' print ' - Available types are : min, max, avg, stddev' print ' - The .global extension is added automatically to the pattern' print '-m or --min_time <time> : Only consider data starting from <time> seconds (default is 0)' print '-M or --max_time <time> : Only consider data ending before <time> seconds (default is -1 aka nolimit)' print '-v or --verbose : Increasing verbosity' print '-k or --keep : Keep all temporary files from gnuplot\'s output dir' def main(argv): mode='unknown' pattern='' pattern_set_by_user=False title='No title' gnuplot_output_filename='result' gnuplot_output_dir='./' gpm_dir="/usr/share/fio/" disk_perf=[] run_gnuplot=False parse_global=False global_search='' min_time=0 max_time=-1 global verbose verbose=False global temporary_files temporary_files=[] global keep_temp_files keep_temp_files=True force_keep_temp_files=False if not os.path.isfile(gpm_dir+'math.gpm'): gpm_dir="/usr/local/share/fio/" if not os.path.isfile(gpm_dir+'math.gpm'): print "Looks like fio didn't got installed properly as no gpm files found in '/usr/share/fio' or '/usr/local/share/fio'\n" sys.exit(3) try: opts, args = getopt.getopt(argv[1:],"ghkbivo:d:t:p:G:m:M:",['bandwidth', 'iops', 'pattern', 'outputfile', 'outputdir', 'title', 'min_time', 'max_time', 'gnuplot', 'Global', 'help', 'verbose','keep']) except getopt.GetoptError: print "Error: One of the option passed to the cmdline was not supported" print "Please fix your command line or read the help (-h option)" sys.exit(2) for opt, arg in opts: if opt in ("-b", "--bandwidth"): pattern='*_bw.log' elif opt in ("-i", "--iops"): pattern='*_iops.log' elif opt in ("-v", "--verbose"): verbose=True elif opt in ("-k", "--keep"): #User really wants to keep the temporary files force_keep_temp_files=True elif opt in ("-p", "--pattern"): pattern_set_by_user=True pattern=arg pattern=pattern.replace('\\','') elif opt in ("-o", "--outputfile"): gnuplot_output_filename=arg elif opt in ("-d", "--outputdir"): gnuplot_output_dir=arg if not gnuplot_output_dir.endswith('/'): gnuplot_output_dir=gnuplot_output_dir+'/' if not os.path.exists(gnuplot_output_dir): os.makedirs(gnuplot_output_dir) elif opt in ("-t", "--title"): title=arg elif opt in ("-m", "--min_time"): min_time=arg elif opt in ("-M", "--max_time"): max_time=arg elif opt in ("-g", "--gnuplot"): run_gnuplot=True elif opt in ("-G", "--Global"): parse_global=True global_search=arg elif opt in ("-h", "--help"): print_help() sys.exit(1) # Adding .global extension to the file if parse_global==True: if not gnuplot_output_filename.endswith('.global'): pattern = pattern+'.global' fio_data_file=find_file('.',pattern) if len(fio_data_file) == 0: print "No log file found with pattern %s!" % pattern sys.exit(1) else: print "%d files Selected with pattern '%s'" % (len(fio_data_file), pattern) fio_data_file=sorted(fio_data_file, key=str.lower) for file in fio_data_file: print ' |-> %s' % file if "_bw.log" in file : mode="Bandwidth (KB/sec)" if "_iops.log" in file : mode="IO per Seconds (IO/sec)" if (title == 'No title') and (mode != 'unknown'): if "Bandwidth" in mode: title='Bandwidth benchmark with %d fio results' % len(fio_data_file) if "IO" in mode: title='IO benchmark with %d fio results' % len(fio_data_file) print #We need to adjust the output filename regarding the pattern required by the user if (pattern_set_by_user == True): gnuplot_output_filename=pattern # As we do have some regexp in the pattern, let's make this simpliest # We do remove the simpliest parts of the expression to get a clear file name gnuplot_output_filename=gnuplot_output_filename.replace('-*-','-') gnuplot_output_filename=gnuplot_output_filename.replace('*','-') gnuplot_output_filename=gnuplot_output_filename.replace('--','-') gnuplot_output_filename=gnuplot_output_filename.replace('.log','') # Insure that we don't have any starting or trailing dash to the filename gnuplot_output_filename = gnuplot_output_filename[:-1] if gnuplot_output_filename.endswith('-') else gnuplot_output_filename gnuplot_output_filename = gnuplot_output_filename[1:] if gnuplot_output_filename.startswith('-') else gnuplot_output_filename if parse_global==True: parse_global_files(fio_data_file, global_search) else: blk_size=compute_temp_file(fio_data_file,disk_perf,gnuplot_output_dir,min_time,max_time) title="%s @ Blocksize = %dK" % (title,blk_size/1024) compute_aggregated_file(fio_data_file, gnuplot_output_filename, gnuplot_output_dir) compute_math(fio_data_file,title,gnuplot_output_filename,gnuplot_output_dir,mode,disk_perf,gpm_dir) generate_gnuplot_script(fio_data_file,title,gnuplot_output_filename,gnuplot_output_dir,mode,disk_perf,gpm_dir) if (run_gnuplot==True): render_gnuplot(fio_data_file, gnuplot_output_dir) # Shall we clean the temporary files ? if keep_temp_files==False and force_keep_temp_files==False: # Cleaning temporary files if verbose: print "Cleaning temporary files" for f in enumerate(temporary_files): if verbose: print " -> %s"%f[1] try: os.remove(f[1]) except: True #Main if __name__ == "__main__": sys.exit(main(sys.argv)) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������fio-2.1.3/tools/plot/fio2gnuplot.1������������������������������������������������������������������0000664�0000000�0000000�00000010712�12220322320�0016727�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.\" Text automatically generated by txt2man .TH fio2gnuplot "07 août 2013" "" "" .SH NAME \fBfio2gnuplot \fP- Render fio's output files with gnuplot .SH SYNOPSIS .nf .fam C \fBfio2gnuplot\fP [\fB-ghbiodvk\fP] [\fB-t\fP \fItitle\fP] [\fB-o\fP \fIoutputfile\fP] [\fB-d\fP \fIoutput_dir\fP] [\fB-p\fP \fIpattern\fP] [\fB-G\fP \fItype\fP] [\fB-m\fP \fImin_time\fP] [\fB-M\fP \fImax_time\fP] .fam T .fi .fam T .fi .SH DESCRIPTION \fBfio2gnuplot\fP analyze a set of fio's log files to turn them into a set of graphical traces using gnuplot tool. Several flavor of plotting are produced .TP .B Individual 2D Graph Each file is plotted in a separate image file with several option .RS .IP \(bu 3 raw : Plot the exact reported performance. This plotting could be difficult to read .IP \(bu 3 smooth :a smoother version of the raw print Using csplines option of gnuplot, the rendering is filtered to get an easier to read graph. .IP \(bu 3 trend : an even smoother version of the raw print to get trends Bezier's curves makes much more filtered plots The resulting graph helps at understanding trends. .RE .TP .B Grouped 2D graph All files are plotted in a single image to ease the comparaison. The same rendering options as per the individual 2D graph are used : .RS .IP \(bu 3 raw .IP \(bu 3 smooth .IP \(bu 3 trend .RE .TP .B Grouped 3D graph All files are plotted into a single 3D graph. The 3D plotting generates a 'surface' to estimate how close were the performance. A flat surface means a good coherency between traces. A rugged surface means a lack of coherency between traces .TP .B Mathemical Plotting .RS .TP .B Average graph A bar graph to show the average performance of each file. A green line is added to show the global average performance. This green line helps at understanding how far from the average is every individual file. .TP .B Min graph A green line is added to show the global average of minimal performance. This green line helps at understanding how far from the average is every individual file. .TP .B Max graph A bar graph to show the maximum performance of each file. A green line is added to show the global average of maximal performance. This green line helps at understanding how far from the average is every individual file. .TP .B Standard Deviation A bar graph to show the standard deviation of each file. A green line is added to show the global average of standard deviation. This green line helps at understanding how far from the average is every individual file. .SH OPTIONS .TP .B \fB-h\fP or \fB--help\fP The option \fB-h\fP displays help .TP .B \fB-p\fP '\fIpattern\fP' or --\fIpattern\fP '\fIpattern\fP' A \fIpattern\fP in regexp to select fio input files. Don't forget the simple quotes to avoid shell's interactions .TP .B \fB-b\fP or \fB--bandwidth\fP A predefined \fIpattern\fP for selecting *_bw.log files .TP .B \fB-i\fP or \fB--iops\fP A predefined \fIpattern\fP for selecting *_iops.log files .TP .B \fB-g\fP or \fB--gnuplot\fP Render gnuplot traces before exiting .TP .B \fB-o\fP file or --\fIoutputfile\fP file The basename for gnuplot traces (set with the \fIpattern\fP if defined) .TP .B \fB-d\fP dir or \fB--outputdir\fP dir The directory where gnuplot shall render files. .TP .B \fB-t\fP \fItitle\fP or --\fItitle\fP \fItitle\fP The \fItitle\fP of the gnuplot traces. Title is set with the block size detected in fio trace .TP .B \fB-G\fP \fItype\fP or \fB--Global\fP \fItype\fP Search for '\fItype\fP' in .global files match by a \fIpattern\fP. Available types are : min, max, avg, stddev. The .global extension is added automatically to the \fIpattern\fP .TP .B \fB-m\fP time or --\fImin_time\fP time Only consider data starting from 'time' seconds. Default is 0 .TP .B \fB-M\fP time or --\fImax_time\fP time Only consider data ending before 'time' seconds. Default is \fB-1\fP aka nolimit .TP .B \fB-v\fP or \fB--verbose\fP Increasing verbosity .TP .B \fB-k\fP or \fB--keep\fP Keep all temporary files from gnuplot's output dir .SH EXAMPLE .TP .B To plot all the traces named like 'host*_read_4k_iops.log' $ \fBfio2gnuplot\fP \fB-p\fP 'host*_read_4k_iops.log' \fB-g\fP .TP .B To plot all IO oriented log files from the current directory $ \fBfio2gnuplot\fP \fB-g\fP \fB-i\fP .TP .B To plot all Bandwidth oriented log files from the current directory $ \fBfio2gnuplot\fP \fB-g\fP \fB-b\fP .TP .B To plot all Bandwidth oriented log files in a directory name 'outdir' $ \fBfio2gnuplot\fP \fB-g\fP \fB-b\fP \fB-d\fP outdir .SH AUTHOR Erwan Velu <erwan@enovance.com> ������������������������������������������������������fio-2.1.3/tools/plot/fio2gnuplot.manpage������������������������������������������������������������0000664�0000000�0000000�00000007712�12220322320�0020205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������NAME fio2gnuplot - Render fio's output files with gnuplot SYNOPSIS fio2gnuplot [-ghbiodvk] [-t title] [-o outputfile] [-d output_dir] [-p pattern] [-G type] [-m min_time] [-M max_time] DESCRIPTION fio2gnuplot analyze a set of fio's log files to turn them into a set of graphical traces using gnuplot tool. Several flavor of plotting are produced Individual 2D Graph Each file is plotted in a separate image file with several option - raw : Plot the exact reported performance. This plotting could be difficult to read - smooth :a smoother version of the raw print Using csplines option of gnuplot, the rendering is filtered to get an easier to read graph. - trend : an even smoother version of the raw print to get trends Bezier's curves makes much more filtered plots The resulting graph helps at understanding trends. Grouped 2D graph All files are plotted in a single image to ease the comparaison. The same rendering options as per the individual 2D graph are used : - raw - smooth - trend Grouped 3D graph All files are plotted into a single 3D graph. The 3D plotting generates a 'surface' to estimate how close were the performance. A flat surface means a good coherency between traces. A rugged surface means a lack of coherency between traces Mathemical Plotting Average graph A bar graph to show the average performance of each file. A green line is added to show the global average performance. This green line helps at understanding how far from the average is every individual file. Min graph A green line is added to show the global average of minimal performance. This green line helps at understanding how far from the average is every individual file. Max graph A bar graph to show the maximum performance of each file. A green line is added to show the global average of maximal performance. This green line helps at understanding how far from the average is every individual file. Standard Deviation A bar graph to show the standard deviation of each file. A green line is added to show the global average of standard deviation. This green line helps at understanding how far from the average is every individual file. OPTIONS -h or --help The option -h displays help -p 'pattern' or --pattern 'pattern' A pattern in regexp to select fio input files. Don't forget the simple quotes to avoid shell's interactions -b or --bandwidth A predefined pattern for selecting *_bw.log files -i or --iops A predefined pattern for selecting *_iops.log files -g or --gnuplot Render gnuplot traces before exiting -o file or --outputfile file The basename for gnuplot traces (set with the pattern if defined) -d dir or --outputdir dir The directory where gnuplot shall render files. -t title or --title title The title of the gnuplot traces. Title is set with the block size detected in fio trace -G type or --Global type Search for 'type' in .global files match by a pattern. Available types are : min, max, avg, stddev. The .global extension is added automatically to the pattern -m time or --min_time time Only consider data starting from 'time' seconds. Default is 0 -M time or --max_time time Only consider data ending before 'time' seconds. Default is -1 aka nolimit -v or --verbose Increasing verbosity -k or --keep Keep all temporary files from gnuplot's output dir EXAMPLE To plot all the traces named like 'host*_read_4k_iops.log' $ fio2gnuplot -p 'host*_read_4k_iops.log' -g To plot all IO oriented log files from the current directory $ fio2gnuplot -g -i To plot all Bandwidth oriented log files from the current directory $ fio2gnuplot -g -b To plot all Bandwidth oriented log files in a directory name 'outdir' $ fio2gnuplot -g -b -d outdir AUTHOR Erwan Velu <erwan@enovance.com> ������������������������������������������������������fio-2.1.3/tools/plot/graph2D.gpm��������������������������������������������������������������������0000664�0000000�0000000�00000001466�12220322320�0016377�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This Gnuplot file has been generated by eNovance set title '$0' set terminal png size 1280,1024 set output '$3.png' #set terminal x11 #Preparing Axes #set logscale x set ytics axis out auto #set data style lines set key top left reverse set xlabel "Time (Seconds)" set ylabel '$4' set xrange [0:] set yrange [0:] #Set Color style #set palette rgbformulae 22,9,23 #set palette rgbformulae 7,5,15 set style line 100 lt 7 lw 0.5 set style line 1 lt 1 lw 3 pt 3 linecolor rgb "green" plot '$1' using 2:3 with linespoints title '$2', $7 w l ls 1 ti 'Global average value ($7)' set output '$5.png' plot '$1' using 2:3 smooth csplines title '$2', $7 w l ls 1 ti 'Global average value ($7)' set output '$6.png' plot '$1' using 2:3 smooth bezier title '$2', $7 w l ls 1 ti 'Global average value ($7)' #pause -1 #The End ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������fio-2.1.3/tools/plot/graph3D.gpm��������������������������������������������������������������������0000664�0000000�0000000�00000003020�12220322320�0016364�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This Gnuplot file has been generated by eNovance set title '$0' set terminal png size 1280,1024 set output '$3.png' #set terminal x11 #3D Config set isosamples 30 set hidden3d set pm3d at s solid hidden3d 100 scansbackward set pm3d depthorder #Preparing Axes #set logscale x set ytics axis out 0,1 #set data style lines set grid back set key top left reverse set ylabel "Disk" set xlabel "Time (Seconds)" set zlabel '$4' set cbrange [0:] set zrange [0:] #Set Color style #set palette rgbformulae 22,9,23 set palette rgbformulae 7,5,15 set style line 100 lt 7 lw 0.5 #Multiploting set multiplot #Top Left View set size 0.5,0.5 set view 64,216 set origin 0,0.5 splot '$1' using 2:1:3 with linespoints title '$2' #Top Right View set size 0.5,0.5 set origin 0.5,0.5 set view 90,0 set pm3d at s solid hidden3d 100 scansbackward set pm3d depthorder splot '$1' using 2:1:3 with linespoints title '$2' #Bottom Right View set size 0.5,0.5 set origin 0.5,0 set view 63,161 set pm3d at s solid hidden3d 100 scansbackward set pm3d depthorder splot '$1' using 2:1:3 with linespoints title '$2' #Bottom Left View set size 0.5,0.5 set origin 0,0 set pm3d map splot '$1' using 2:1:3 with linespoints title '$2' #Unsetting multiplotting unset multiplot #pause -1 #Preparing 3D Interactive view set mouse set terminal png size 1024,768 set output '$3-3D.png' #set term x11 set view 64,216 set origin 0,0 set size 1,1 set pm3d at bs solid hidden3d 100 scansbackward set pm3d depthorder splot '$1' using 2:1:3 with linespoints title '$2' #pause -1 #The End ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������fio-2.1.3/tools/plot/math.gpm�����������������������������������������������������������������������0000664�0000000�0000000�00000001262�12220322320�0016033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This Gnuplot file has been generated by eNovance set title '$0' set terminal png size 1280,1024 set output '$3.png' set palette rgbformulae 7,5,15 set style line 100 lt 7 lw 0.5 set style fill transparent solid 0.9 noborder set auto x set ylabel '$4' set xlabel "Disk" set yrange [0:] set style data histogram set style histogram cluster gap 1 set style fill solid border -1 set boxwidth 2 #set xtic rotate by -10 scale 10 font ",8" set bmargin 3 set xtics axis out set xtic rotate by 45 scale 0 font ",8" autojustify set xtics offset 0,-1 border -5,1,5 set style line 1 lt 1 lw 3 pt 3 linecolor rgb "green" plot '$1' using 2:xtic(1) ti col, $5 w l ls 1 ti 'Global average value ($5)' ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������fio-2.1.3/tools/plot/samples/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�12220322320�0016040�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������fio-2.1.3/tools/plot/samples/Makefile���������������������������������������������������������������0000664�0000000�0000000�00000000767�12220322320�0017512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������all: clean m2sw1-128k-sdb-randwrite-para.results_bw.log io bandwidth m2sw1-128k-sdb-randwrite-para.results_bw.log: tar -xf fio-logs.tar.gz io: setup ./fio2gnuplot.py -p 'm2sw1-128k-*-read-para*iops.log' -g bandwidth: setup ./fio2gnuplot.py -p 'm2sw1-128k-*-read-para*bw.log' -g setup: ln -sf ../*py ../*gpm . clean: rm -rf *png mygraph mymath *py *gpm gnuplot_temp_file* *~ rm -rf *.average *.stddev *.min *.max *.global rm -rf m2sw1-128k-read-para-bw m2sw1-128k-read-para-iops rm -rf *log ���������fio-2.1.3/tools/plot/samples/fio-logs.tar.gz��������������������������������������������������������0000664�0000000�0000000�00000204765�12220322320�0020724�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������Q�:鷟Bx-ڒz}LdKU-Z樱v} ''  2"K?_77_??O֧Bwo 1bK)Pſ ?~k?O_׿yu~_kޭH9ͩsws.AKnip7Rws d˩98oAFs;t7Pn5nG $lO%~4iO=%4̹Rp:>5R?Mg8=08B[98ܝ.ꮹ/hV>w7<y8֪韴na|Rn8f|Rھ[n-i.gGMӟy|֨]#Ӫퟜk׵>Al85c[=N4HBͦmTg{%0ͳm3w0^1m(e}j4>vz_uui:{ {Z[ e\1eTA1yݶ19Zh>qɚ<[ ~ܯm#ú}Sr_߶qpO3G;ke;bhs5R)~w{\Z&Z4!4f> bE4]ze43t+6{2/5{b4\_1~7WלX\K͍qzVigNT>Wu+g5`-tN@wk= {Йpk^c6ƚjL!f&i-FX=~oit:pﶟWlt>XRUb -؁_R6ȥShkCcn M<;9=hF3bܺ`383+ňfZiǽ&icc}mƛk 7[ct_x(+I6FN/6q\9KǶs46F68Qki/u}t9^#=# %wZdW\㪮LL BH]ȟϏ| O�ߑ;>+ F-%Wp@t9`}(0@@3tM=~Ώz<'(=i`x^)1.�rFK/0Y؛W ;97 t%5 40LQko(,XO1jUp.?89W Ɍ$= Ӆ c�.{0�{pF@:RǘV2y0|Ճ2nq�k:7 UV4A1i�RglPSO1WeT:`Gj'3=9:agU+�%G.ٓq+32y"gG ~zss<8ϙ9N d3PN1�ޮFm2!?8MNZ2I 'A~4�}`إIZqHbUULC)rE`rpLLIFPhnAp`#Nd 8:PtÙg`� ];dIg1VC�He9Kd3C@DOH8 z775)$/=V(O;>B"}PB\ʞWmվrPb;_x6x9Px+-K+Cߏٴ}{_[$?벾+%4FKGJjDz@+]#JRWK>f3<@X2N0lvܹddHgG.ވZaWOGTiX* kn{-4{ %fP,p zg"3Z:zvk#5X\aRg0FsbG J\okڿ \3Dz,Di3>EhǸiཡܝeS +SM+9tr0w'{|o׽VGsgң{C>6T}1Ho_(U~m/mJ{b׆JѻaJȯ1%PkCv@I9ֹPnGn"WtWVNadX۰C@= eY @j1lfM^hvGwsCp^L=R<MoHk^]$>,�|O++0RG0/E}.a_Dv(8ÏU6|lhV g</Ȝ熊w=rYK3b`qkf@-xX'k7af`�=V?wZE Ӝʧg+rxRokSΏ �O od�wmv- Jžx[h<6<x?mK+c j. a j)o?#snuR~C7~7~Z$fψ1 7c:ӿ,F?&$79>Mb&v"r`6ML"|Hls@`Kqti[{VM{ r>+@QzҌg>ᠦч4Z+GM$Q@D8P_raԁ}1]ӆ|(i2dלt$@C/vQd8`\ �(ʯ�Xe@r7Ggvw+�C<�0W�u�W�@ <܀A'�a&  F=w{@�!#W !?_{8N臌dNg ỉsNاJNԯ=_@bdrFSSq뜃1um_Y@ٵ(Ϊ6p6}5 ({A57ZNSG$(q<+HFqB|5 ([ɉ~Nx$:o9ՋnV-'H`$:o9a@ ՀTG2'z &_Ay0_8 kD‰e&gSH~'l_@܇UGڜ 9 ev3Bk1:_ɖU|SicXZfDÝYY,yzRx[G~PVǕt;9g%3HM+A.%.XiwvdsʶlMl?n6 Ȫ5Yn`' �Ol;JxyjzO�rL0vX =O:@<}?~p Fm?Ldxʵ =N$'zwew6: yK6݄Ir{}G;_L {YpBmQ#ri.U#br5i_*8v4^.Xiؗhm)0 ]کn.sxVn(}0$$.ni_'}%4D(ޅeJWJ=4Nb,Vh =/2O{~gb(hl?M-qTbj/?V7V,ms<x &y4\}f":!f~ip1oV)튳`<wovL^b@{6$ y椭g;iv줐sei,9[N\Fc®8ʣvרl9nZEZKkf+Xkl)~(K,Ȯ<B<dSKH8[Q,]w=8i낖N0 okeRɼFVA}V^:J[(\߮ F]P] 4ꅭk#8�Sn- j'.Ǹrh݅֊Z[:ԺDKvTy%zdg^9%@k)vVk.dJ#ѹg']o⮵p҅];Mbq #[ ~uTyHX 9.w<|- `vq�!psƟQ?V(A?w|(gCX0E_+/7=J(JxŒ-�^eY/YdFr݃60W#p\cYo$? ;Zp茧}PAA�\A"~bx|)O0b|rZt\0{ppd$+Za鮃]9 B?`UHʀ=4`\Ϗ@*'~hv?CP=/N=QFr3<*�#&z#;R5:pQ  Ep p rE@z7# /�ePD0.4.0p]AlEL4085Ae2{!a2{�KrPF^*d%Xkj|=WA@U*;pJ#(q drtFd:g:ʰHyP&Dv8<2RyA92Q9#2:HFnہ0 ZD(F+D3L5# r H4t0QC =LsT4ڐ>?0CN ݣtN_rCHrΟN'?v'UK�lxC@ެn^/k7*NVTjG,^Sܴa3+㼦 D$V'gCbmaB?iyx3D(t"ĵGE_0Irw;E $R`xnH~=pL%8qLl!n7t]6i_kL�6 [1Ov2YQ!Վϰڏx7yg?5q3feWb>b@ns.P?iJٰ Ȇ ,8#Yl b ,FzfcA a~~h"Cbk_bI@mWBv#f]+lzFSx7XpvR-U;2 ثZEajSlEL|37_&ݟ$<- `{I#9SxO6^m{+l|o w;%#;mdzhw;>{(X%mkaZ7\g?;:~u' T4n'ZޕAo'v]4{2ꦼO9ML(v�<e+9%nIe^Ò Ӿ74~2j9b 9^Lm^u(}]Į(%ؕlD:hȭmt#毼RwI%R& dڿ[as?`@ ,]BzЀ- -3p"�ը:v!{Z;r0M%+g}.S};[w)xCg)ڣMȪ!RUX @T9Keؤxq2 H1%{@`eQ; >Ӛ4� bEAHR@8DfLd\ @2w!\.�R7F(  ժ0�<Bיi,{�b.,@JM\*y8a0d2`d6� �A t0 ;�+HurUvT_׵ B[_A:.ᆆdMIH<s.s7w'*$]:3.NLBι"9n+Ȳ@u *l䄺%Ȥ["ƹs $t2h@T4 ȏc-rF3KD  I@Afk, TqSWdU'2g=FiT8mWp~3,"g7߿ς~'<z|3%8~٪eFzp3N)Dp‘Aܬɐ Q)3Ewh0pAH tK] ,}mߺU"D![u{Jjٯ |WcXdl+M=N}e@#Z8Dh wkN1Zi!HKNBٻ9τT EGZI~5m0C=u-3N-XFn V m+M爪q+ɲTPbY@i]RJ'x /C<'YI^GK8ŠN|ynrT6+!?9Qjf1<S߾Ff<^z۶ ߤzB$xE85]JflKTng36{oD #ڼcΡ2,+Jg4+Z3@)M-Sqےb/يV@V 7_-{q8+@QNNa.YeTTYܶ@1NVX֛mnpSZsLpg1'$j%׏Dv Kۧ-v*ӾR&TH]w9uT U`T;DmӻX{VGM)InRbazgAqW.3y w<Μfq WbL8ftn;ofK+{)kRO Y:=ͼ۾>C mT)XХAA?tŽO jBM [NosәdNt[�0Wi--l-H +ZvN .muA\B5).kI~Jq{T(—ac;7;-6Cn|LZOzwP?{3"#yJ< GI - W3i4'ϟoGGq?0N(䫽 н|ab@^݌Wu3JWO jpp,2b?�tA^>0[x_ #v#ju8PZ̧ѯ�<GAQA,dD3@`d,gʐX`aJ,.aexW@F"ah{Y0UˎQ _rd&(^{ZE`P*DFoIPes6-"<UmÈS=�5z�c^B .TϨRm�'Hq0\b1@E7/Kn`4L] $3O>'i L <9<;ԮN 9 .`vy%цL̮Dv9L_Kum$%bTEĐJCcmщcð^Jr d2BH~xhz1 K9Gc52Q%A:" bSz@�ua0jF1Q×t:GS2`QTԥ!jHP"5cS;Cj %ATɏDW^ώR+W2$؈b+ZH%ȏ~W&{)i"{:%+ *u,j*Tbd#RiDDրR{!hyu8;/Ҹ# w-[)$YvNJ!GzX ~|Ff+=JSkU6(,\ohaծLv|3+_2)}{oKa+0}7Lx:1 ,y7@q,Щ*uVq#O'E+~$@}_%tթ nfmJ[H!Oj`PҘ2b+N䑶\#a΋y^٩hݛz>f:b3Ç-GIGVĖ뵐 ;/U:y*=ӫbw;`u~A}e/)T"~(֢>EgSOF2CWw;sxD$OHM-s?2 "6҄(pY.6ShknKb'bN:>6o!M|ox?HH6#- :F8u5h6obиIiW2O 1 a:ydS5l|.x:vt\7IMvpzD_&Fbu O)PZ7qS tP M4pT(1+`[l#HϢCM8>fStOAd fO֞|�v;20p�䚘9�]zZZg4u59�)Il%LS29` v)2fյ`;ékOM`t G()Q#6* eCQ=<�v ePs7)U]u�bPr-FCZ�4=* H6F"*bDc1Gg3hgJ: W\@zr:/B܂ 6;� MNт0L`|p ,U5+2q8q7E2n^,ҫs&~5HEނnl] gwsKrV>/Ae:9 W3ξ/5d*Kl[gȮt2g՗!΢ʞ:!kz ]icH_×i^ U^5}t4WExrJk#z+9}\kWW!=@Z:6P:zPz} 9,2`g?)Ox?xOe;Q'1Xr2)dc3gx?'bxY;BÔ RW;l$;9PIdb &t8eC / %zvs"Y;<pG\XotcDcMKt;t-مvÓ ݴ|YTXӺq$U_O)Up jg|:7V' i&js6N +mulS@YhEI>\vn54̻ꓨ~ىV"t?F<G,NRe׬&K|(sFk&g3JwnZm+đ uK<WVNUQ,l*d8@tվI]{Tmln~9:clz,8 ď,j:vyO4Rqۡ[N9 up~IeyZъn<˂oXHeff%1c=�-9 Wd#c @�L֝D5)O;fi(6P#D4n09Jtbه9ThRY+u><7kl՞MkŇ![4RfZD)r{w2+^P%}$HT!'X#hF$Fј>0V{|˦2 m}$0_G+%R+Y]غcN(q%~}SJRT~I&8ۨ-y7ӎےr ia{a:3]%WJ!ؤcnȵ7xWe3:kTdlB%ًL u|DzɊwqFy 5p-sVT࡬:Y9PߟbVφՍ3f�?{H3f6y+` /x ̘݅] Qt!.x.\5"uÁt0 h{JG2ǁʮg\^ G3_~�4v%Lf<cqa\|E@@x\AÐW}]dC neTf? gco)a*@V1HUAµ,~8T@�Mrv`MU1ȼ~Js9pR8R@#Gu`)1b~?C=OF Epph`ESCtGԡbQީ>|ٟ؍p]q.M TrКCiTM:,vHFd1A/35|nd@]2ܺCTb<FVcHu}z D. Ier6icd(ᔉI}m@E%Q0JCFA eܣ@ݿ%1ґGUpH'3T@3W(`#q*ΠՀS U�FwZp1*)@� :>y\*A@KXC�W]`ť,бө?ˏ? ]|Ҵid|"Qmxd52*=,}~Kq^٢4உ?JӇО-*M_kzysݷCIbh?4IIHiqՑHӽ;\eo`PVPɎ̺!/Je?o.o^=dMw _eӜ~_l&mD±#H~i۩|#8ؼÃ`Wa_goΔz~ v Ӹ$"u88@JW2ޘ:bۑxq+vA [W~uZᣫw=}/{bod4S˓7 ʄ641wz3wghbU+BBȴ ,,|c'|6TW qRD Ԟ޴gzj{2-z֊\*P+z@։Nq%j]?*VO2*?'G@hVN�^IK.J i~2+@VV"Q+_/D䑍ms}>i8L?)@Q\Tn(x/x ,$|0Qjb`ȏ xN׋UeX%[ۮӾ'Ux٪k ,CCՏxy]~q:?Z)dj&j3_H l-hDZŕeWrWTm{׆\R ~,zQU^ujؿXc+LSlMS"M #j B|CD@z*ĻJ#�u*�ӿ⮿ pt  M4�g*( lJk@nRFYU<WRB?)=)PC,P8`R %q �FJg@}@n6�L=u�Q0iKDyF ,\e0jQ Õ:4P)�PZ!I"$B"B DEV>.#|@C5 0."� 5'a@ѵ?$B t (2\3@zfhDi=@ w1N* "+rBbC3zs7 wnWNL9OyJ%83o29%zkq%r}클rAB>N: ÙՀZgҗt @skAܪ)rIN Ύjfê DX-gW'2缪dW-g:,9U-kU@ m? z:h=+HS*^Z&䥗X=8- g7?<G[>&`~adP{:R<[O ^?hU+SsVqRBWTvrz'ERzpzH]6RSDex"ʾo ֕`WR5WZF4}5HN4۫9مBC}b8rT]BH/gT؍ʄ#XJ6(X UO=6َ$w{nٖOhr DL2Оd3ٜ(o9 [wYNHY2ȹt-syK(ӻh֮Ll<]3[fS!5>q2OlŶ3I|J;K5u?}4 e}RPS:eA;>~W=Aw|I;P?'b-BU_U""29,bNAwxnZSGL7 C(aӠ_9LcbN><J!Zb8VgO$AAΌ|*$V5^1>kP= VIg\ cd nY` NJֹ;R`z^Z͙ENFĥiCVk^Y4)X~BkrTD -h6q<4w|`6+[5g:1V2E4SFM^Y)Y[wN)JօLx>E]N2[Wk{a6w6/d6q$xNsP8iބ*8=fjw8O)ʮ<s߯wm.ll ҽӹu @mM�slكmzO̊A0)P߽^J!WΗM fףRh<׃B)!<̾5+i`GſBW5c}V# Fa+U讄h뢢9(]<O2. ȕH\l-fs@zo=R�Z JW3Vr-1}H0NV"8  k� :W^u�UtwAB�mUq0R^'(V Vcj2tZr0Dfa$&T JՃѨH&z�I #Na1enp)=ʨ2ᤜ7H?:!rkgȨ Ɗ:H_:~0a +T'=�< K�;C{p}8ߌԯibPHdS@2X/U1JfҮRACKØJ]L(5.||pړ0 %= %6үPT4>=!AW8Q>Ҁx1ԉC`#Qlh' 0jEun<hU!Q R.37/sG?Q5Y##+Cq3q5juN'?+=?kմX)+ůd_v ߾i 82W|$>Σ1"ZR}-VZa^^P\;Tm?y/:: n>5ikӾii$j妧oD}lȵ;ÝMhHmݏِ6TwY ($YmZin$7-ކ7c-e�>ѹ<hmZ?}O3wbP T6sԏP&PG9t$#/D84v՞>lvmu3v�s)88+a.u`r~bΉlshCOt:⨁rBMTbYI 6^oS-[:L]@7|vDf5HpZ4F;Сnx܇ծ>(;ݴ&wv*NH(%ځQg4ݬBe]A)ݥDy }Vv5&WI1FӒZ\M-P, ̻gl,H@wz-BNB]y|v-+,GӣAN(2Q2O]oΔ^hYƝXVXV# 48CN�̞~/rݚ/q)z64޾+ߨ+ 8 .?ix{y/:A5xa85r!=?@OOH#Li >�@x�dnA$iSI@\S !Df]ou9{�!�4N�� f�`C>?3"`u`u7R{ R 9(%`R�HWB0 -@�h ]ϵ�\{�\` hL\2@f?v j�VQ ($@DcK r}C)@j�~27Q~ hh�\>:`BS{2�1"_a(�jŹwWr|չzջp6XN9!7V=2v }A93WEg:9|_<ȼ=q_{ Ӿ C ȬufsF3>4P]%~pSsfCdYC3g*5pj ΄ N/ r߮ $U9v5\p;T̃VtB{7Pz!m@z5 M uHKxiPuK!By59{pYog?<W'�zcqX**S!4cY ri-1�+-BFK]"6kA}I햣4{u+f |THGd >bg�òugWs=yRNm({x^Stj)zڇk/99PA%,${d_=Zg4Ff=К915٥A�E>M| }!4}"s̍ǭɃ.Nwdqj8"EVgYDlވqa /g &S[q!$pM%iVoug�H *ִk\k #ms>W+";!ש</+x[QwykI,QQ5v}Ey}1#tgXbeK:n ]W3<sW?UQ{,?bffgSWd~Z&Tfmk1skLY1nZy z 7.Fu&;<Ѫd{PsUFZ)-dlWAjL/lEWB5 En5VjîV)F癝neZ[F¤Y]M:Fn9窗&.oYP\\C#VwYQ)U'E#Iխ>@&;hc6XʶCqtդ^N6ꮼy6'a%3q�V2afDvNײ6ܧI6[F!CmDt* l FTf(m.z9ow6p<UkFL+<תm Y1϶|g;D3@$i}I)?l_,.�9*?(]ˠgO俽WyR097˘],nt0< eH$CEq@~h�V2Z\Q_ լZ< f5�\΃qr/^ˁ"9XXH D#w":xW]誣P'DbB9?za+qy*bT>@K5/Qn1(UAFt|er!y8@MU�cK1Q= Jb8yp09�RT 9ʵ(6T+ʀ#M"g,ʸMbhd;@JaTI0O";=&>(CKt1x6QwQqjz�Ю2ӻϐb,&AAF_jBư@G7 4\4rZW@G4�E('un€I,!GFVCtACiƊiBF<+ ut;G@](:Ǩ�J=2+YjVA=1%(1Q#9CEz?GCܹfl>PNί`<3p~t;(><^}}ke; `BI]\'أ[+#gr zݼ ͩ,/K!{D4~GNXǿ>̃"9YH9N޵ _S f.6 nS3 k'2BvFjgma1uZzLޘy2nY ֻB&Th]VALN?R$cRvw+孓){xz;G=Tj+Ge*g|Sw@E4zi84꾨Gkfc:L)eȁ^|x jrs6\I~h |_LŘ?6@ɇ*I5jK^S>G}n-|J790~MMl/6CMt M"r: N 6{N% GU"=|Z7 Jl ND1n6poYP%SF585wmNOvPPEw`QX(B{3hW2ϤgJXAE/'#z+HHi8"~ u%�v]Irwfz2 :B4vOvB3WhSE- <G$/Zv~LYWg$qNO,.9 iwv2trw|&)O~5ÙO!y4\2.i^}/Ww AHsrc ly ^[LGdj�0@aM_Ul1v# R=+@Sy0I 1xz2#] �GM̛q0�RCF>C(#et.UM �F9d?¨v˙hE/aLd`*F$F3m r<TEˆ:u@eC .V29*cq}ȡA-!p9�\ *_`(pp7P`)!Bj5 >rH  eM(WWM'MyW r '◪)NݓGaݍd[4g-mש#VGNd3K!rA|] K2lAgzLqb| #STKxfoAvY8S,!U/kU mԆTq { rHZ5(qziZq~D2ĉi}k?GZIQ\|[ᘫ"}:*+Ds ky&d,ARt_ ptzǢ39@zC:sΰ l{̙6|{j)wqF6|f:ÌVI $~jّ()8=@T}2zWgb&{'l , ֯Xf6h3#N0sB'z&7Yk_c2WZD̳eԇtmZ``W+u"]]*aD;}nO)g:/y0-_`pH+:խ#vo)NR ϣn JE ն鷂J P CHo4})7ʂ>'MbnxʍZ:,�IH$!Xc~"v>fߗ!u=\9]W sxt>qazݰNrvRpoh;ˍ_Bb $c<N91ܸ^Jt/6GxD2bہUcD8:D/Y2 Su6T~dwnl wFvz Zenʨ{Mfӎ!3\߉r gDΎ^gLs!=Y]ʮMLi4.g@ l%n+?M}嘝]ږmît8 l!ڬ ur˵tX]Jh ܭ)UgadևtL'҇U -z(Iݿl+ۼ$PU纵VέX-AR֝o;8|F50GtDkѣ~hv^jZBL@5?6hݭV۲UVTPaU�߫3kn?f;u%3.{\yj?X-<BT')P Tf/󰃁U~� tbF" tg̮4y(`xT{J H\H o? `o!Э@F2 BV3O0V9c{Yx⌚㔯0\p*,@N"adA<.T X_шث@W #K�hLiErܨbřW3n}ܐ:HU5D[AtH@q^?0Tn6k.1A"8PS>cL5`py�UZ#/�_1rTy CFwdx(#qa<b@B A!c?0K@{"y`' d6D'41(ɰ<M-i@av�4\!bI:їZ0=o;@K. ,h%~0ˏQT3nYKrPAHdh4/O3`!m`$zs5% q|:RW4�ș5�4:Ec9T i`Lz!#2 =~<q 8g:YjOx?+/!tt€GO"@m3/p3m�D�>*q?}=0$~ V>=sdM>3XgTQ h?G'Y+~RMFhR5D+sZ!E3?s;Qɔ�Gq#5 -{%h=0Da߼?^@1NζX5~G#f1Ve0|NwϹ;Nae4IWgy*,qR-x jZ8 o2[T~=M(I*ў 5ɳS<|8+I34\t8+6&6{׹5ks�`*e؜XENY(|mʌϭ;5 �3um ε2jCl o>D=<Xb+4:mUg/8o [߰!u{p>Ӡlxn]GIjAC d<nzFIT{ ʾ(@,Oxݫ{ oHQN ]{&"Y(7n=Z2opYMxZwk!{τȉ Yf<n~ކX}.P5agy|q.V梼Q@zQQ)ހFϭ])|oh&7gQts+LQuchFtOќDL4x&)O~A&Л~[q! @xiOn]8!̀m�9? 4$�@d$ۥ?%"du�Q]A@Rz(/9ӥRT5@HS%C@FĈhtH QUT #!6*t�"^ads5�\~H@2`.E PeP* )}F(z_\{�\? $r}Ry\ @H:u.8!Bg# '0܇ĸA>*< 2B'&TW*"M 1__8.L ;Ǚ^ gwCI߂ 5*q>K_' :21"3r@q~7*bqr}5 aəWqFΙ+Ƞ=94Y-g:9CAǙ:Έ= _ X%TjW#g 2" șF |wjsz#7pz20JЬgׯ .'Y[c84 5<Ty[>n,7W⭀$Y;%/BVqW 3ڃTRlK_;oQ<Ց/!ƌDp‹I8=O$^UEhSO#~o3:2_eXxe-bOs'GLA1;،Xd3g˰WBǴf{˜Nw7i)|a?V*KY MjχTs0/:^k|ReN C;†DjVP!<h >6If\{?t>1jli6sj3rʇ^eiJf/x2;r}_*0yp& <{Y @f 7SU緳PЄ1JWqjxZ{8w7e .;sy:˂7 ,'ڎmar'+[dVkv]ҙN>)[#"7mdc9M\_.]@m 洄g#O:;hYIz!?$S'!KI2/4OdDT٬gu;O4MI%HLH$h5vj攥nI:3c%udߪ3 Zc-Uܒv( f{)\NRmRGrִN/ŵuS[J v2hѥ\`GhMvk./m/-da&ƫl>\8^$k'b5vKq֔,;s81`ݙLttTZj;.Y cKIM?X66sb5M)EtfJk C[+ֆ͈^p얶= 3ᐖMږb8,[,7k[_}?O~6G\�X_9_Za߀p;c"y .0ٟ J' Ω.w"_`@~lA`n?^}@j�ma|zEsȩ�V}?r.q@.2V rœVaAzPjt{ڪJ1R̊&Q:J1|;܇rFUeGj TG!K.!?U2�L2pjcL .oT?G$sp�@ #!cP?hhH?C<5ڂITt@jP\fʩ�=I]Noʁr79i�ԦA᚞4 �.5!ޕT]rޒP@º�t\L%'6@H"'5b$44�) F4P5@4U^&i�bx~ Z0ˢ 7{{p�`.!F.jr ܂ kL%w@8t�A@q 9jؐe 󧓥?>F|·'oo "y6pWl(:ỽ c|6 eH_g.9~A/0;=Ї|O y:h߯u <GYf$yB|{`xMKFwC9jIP pLjW^9ljYp͆ņ ӻ#`󍁗g#z9+M [g0o͊Gӂ /'9RMxSt*'&>l(P HGժ!]r?FxTI[[;yfCMT`0dH}M`Goxfկ9rphsRn<bݬu yռߨGfb=A&c~|PU?#{WXc`i $'y%je znTO|ls+ [b(9Kfg ߏh7V_mu;wk87}E2ːTF$jHӬ3"xU~yb~mh*^ b5oyZӰGͲ_g-iWSYjoSۯtLGh^ʮaFk'[TU.ҖhoH"SˣQI@zN» `X37sFN2 )MdJ� 2ϰ71z?D?!=҇.HӥRԊsٞieu4�)|!jp=l jdnMfrifضV.@*8BQi #KDrHj�<n+@שghP@H bFWF.K2 b01g_-g�u)2k9(2v38c(S>_dW ٥AR.θ/ 2K [ g֗B}Τ nj'dwL݃:udW2K&!tHt3 AF{7`1`oAP*%EzLP33KR N Tgg }4oO gZ"I H}FSz|?9~'<zڒUTD0e\.\b}d7a}7P[:(}ZB8W jѳtg4H(3&XlllFדG۬!hI3y4d,XKl>-ddgv@hU)'ƒOG/2S{jLm /Wfդ DvTpk h&8{b9utgt?2e1de#3!Zw4'bvTwkT`9x9N&Ӷufk4XVt~w%Owr.Nk#(̝FI,(l =ωgcZry:Y&큟%U&9 VwJ5 Q8uvZJ+Ew kᵻwC]iN(e+᱌d tO 6 md/k2fݕܵq2�ެmK-xTUmljYA+s흗bgZb}QMV R?'4 iFxtGX^�Cy uKN^Xfq~;5'0ZН Ea <pbKIYWA#ފsoK½-LZ4K[("v,~Yq4talzǵu\lmt%;B/VvcE #Eq6/ɴ'Xf{|hтl"HO>5p[@[:`vjI𶃍kst׵tz @""cN)z|V^c)ֻ Fg/nZovm.1EnM7vU+Y۵_hvVlO%76Βr(w{NEt_xlBi\z٨m1eù7\rDސ#8!y]XTst1=2~w¥۫FUz/İu6;^{nyv.5O5L4p'}_?Pl0 ꟯3I ߞȫy]ӡEK3fr~ Uw(]$<WJg@_a$~p!Ki�R*LVl|jZ;t<<:uG; "V"ªV ƾ]@~Ѯ~_usʪcX^UĨUgWNx�:02Us@R70$��ť'7 у0h)r060=(R`~ƍ"€QAz at;0ȩ3ԃrr-3/xz+ջi#<7T�w+}�ْ(Ԁ$n0HHL4(cjP vi"~DK9K&[AG@Y2A{pZ9P $ ) F 4\1v$0Z0T_HF*|Z}~ z(ɠas/�T�K� d5u&2�b9#HP<8Es P+L6 XDC aS4xC:Á0t10EK8 B:54lπd?/+?+''ȐZ2 TW6a?1Q#:t& ޻Qa$<OKe+oxQg5DZ֊WPlA6a>3ic`0YcoĎ`;`~~#`G{0_Z9(kC l!&zΪև?"'%w?"Sjow7F@2nv"k߽Tožl:^ Tpg";7@ה¯67+e%eÈ҂R*B*~l`%˳&wo\`'75wJݡ,kPf ?QwLL$of3EAi+!J̔o*6n8!|5Bb:G^q*(h2?5:Qe2?>6uZ)1rKDQ<F:sG|c6W:~M=gn/6Sγ!gWT2kb]yfE[[}>7[|MmEb~V,[ѣ>k1WtCF!Q\@ 9S+*}<VÆG7F6m*ϨZ3"]k>�cC+'X/6 $ ->`[W^1V}2yoh|YO%} jdG.ԾXyX?<?|0_?XTKBzMe(@ovMT�uM  {!>FךPӚ(C�nbKL&� �^+Lt�'�n @uIu�$@! �NC�F�a͌6 R+BA�ـgfs7wG lH^"30Uu7.]0,څUC lC1ɐXrq7@3 y�c\ CcKC,1<!`S :o+1Bc08hFMMt�0BrNޯӃCy2Zt ǫ?:9o Ъ虲VE@^ <\OjmH#kU8T5ȫVE(h2JΛuVr</C ʓu"s~EEyqSqv (nϾprߪ$dRFCV'"dj@BN^= z^$:1$WһۥCBOȜtNl^EԇepZYM''?+Pn\OFch ^]>aC\a4Qpx",ެI˜((sbZ~"IlrߝT]@#Z $[t?I+mG6<K_cw)$w:(9DRkTD)�i#AYxw޲KW姀quYi=vV\ilwD*gy xмODM<UL,263X\Pm&nk vF꾝#+ Ӯ N6Y#8}Ѻ](iH4&#aR(]@nNᱥIv<Jm-[N涙ӝF`Ͱ*tqR-tF 5Gq HOtYE.5^g[8uj;5QVtq<;R'亴>/xdٜl)`5{_#pvJW5fEfP;gSiM@4$lzT<oծ>k<4[Wgg ѭڊb;n]iN9btkiHkݲ=vvD-~9mǍݵsg+5Y IZhӁ=_IMc(f3YT?CU>ΕS.Zj;R/4*Jx0(F;VΡ6ƳWD؊L'l򺗤֒S0dijЖV\f˧Xy`|vdt`-t{]iY@⤡!IL|؜oEPP?]?Va\ 4l-IEkZVj_'gd`2KA8 j.%[Kʊo:lVw>=2TZв2BK4g{%EzXc}3WL)zPl lg.hµe.%Q*w`q+�ǜ@(W!FQՖk8ZQ0tJg9 ''K2@c!3"VjX}w}@oC0U\ [ф`+2Fd(+=z0xUN>V3\U *awFrIՃ7jsuЧ`<xS`FrbZcؕS C{"e3|T*�b(C":P}A190P#W\_bI`' |. uzɩ2!C70fS7 k2EJāg2.SI21S @*?d쥡Aȃ(aW:5AT5P<ɐ;IBK+L 2,擱PR ! O܂RCgڨD18.0#{PL�4i`ܢ#j~!5+ޠނ40QuqKސ{p0Duŭ_c ĥP$ :g%O[>HuH"' z,&b͊VV. J=DEY(VQM4;U#N4^=v'#.T͹ w`H9zXyp:[TMOL!_Ire>6uǽ~~#_(o �ac -\_o%Tݰ{@4g)W-f$KXhw; 8JʮvIJZ} Wu^P㹏fgLLJkv J?LB̓>9=׎9MxDPSAAձ8R<aTBPW{D᮫3S۫v PJvꦉ-jAO[wJAUT19HmA@g/#?IJ(a!b: DU'Qj13E\Flus12})? ѡ;=emޛ3K<#Oz45ct}79 <š?%ވ= =yIYJxsFd+)>xURcWyJ_x * ϻ^րPMP :|tG#c;xVOݶ&dž>5=+Sg >?e72<9Ԃbk8'BVtf;jb L iw}fkJ=AR{Ҭwfھכt3o⿟僩ȞUL@�PjOMCAb w q#Zk�LSeY Y"SX`*/>PW*!Tup�VWP ~7Bge2v $! "ba$ty |:N>cJpMSUEpmZNoA.:p4*-di 9uF'*p`X5I3#Ӄg�B)7h(ŘC8 6Z=cQ+M+PЪ^8{@C]44t0L}:]T"PIC/9{Y+NrO7PDdřu-Ui ]t]Ŏ}|mD]gAk\C'MeGUD_A}In_ H/K2nt!@|5 m(廃4nXϔb]B8֠iq;U2WNw5lq\s|VHbupT̃UCy Q>TIߧ_F?O'],s;\exT/,Jz { Ȅ*>: 2wH/׳S:AKѶ %ZQ͸{WFD}`TT,;DdEs^*q@\dЄ"s_؟5I~Gzr.Oɔ4C:sC5S-9U\%[>uP(Vm/Ͽ;)TMqo(-A8o[[N0|u躂Z%ghe7PUSvJI]ƛ3Yɝ:kiEqLy z50\X ŸG1O|mWhr9s/47\և8X*+VgOW"qTīYJ陣sw7eۦV6(8y/ sfuY`9vNG-ڻdsO_;1d54ytu n^H4@N:&D.)u%<T�k,TwNQNX9OD+56KkqѨPs>hJj_A[K4xzAT\M곔_*jYbw{+wEs3xvֶgPZf*rhA%ẝ̂6D�8NJSp׮$ӆÐHɴn]HsN'PX"N GM[cwrgp;@5繶V=رlpv́=q̛I*7INqSi M{_y(Ӎy՚ Y\59t�m!X Rzu8иv<]b/0Z6P%9Ҧ|Y^q?o#&yurڞsZpK�Jŏjy*0:<@tnȦ e? }Y??郇K/zADįtmU R 5 p?[f4/^ E]D / >/<W_�\sCJ [j+clp-Rജ(h9YY fU1T/}9b4FpPR^ 1U  sڪtV5̪aX0|U p,7PU'FTYIe0]BEuFz� 4Z]+`P)&Ep`e(/# F%3TTacDюp( ?Hg|C.x('3SByQ1�~Ν]=]HNpQFŠMUT0MŖgd`0FdKa( *d1R- íYii%qJp�.0`$1RDxI xI D,t,I;`(_**7 >=*l%bqT-tMh!i`4sG.KXt|? D$6i�{ C~Tiqj11.+e *?ΡSetY'|Vmvy*G(t^ [9eI&J5g;0}G lX-~C v.86,폧C?0Xiŀ^ўc5ǟWHM =PB=W/]Modamfp7Ofхf6-~ z7@s { u *nb<;ճxjQKe2ڿOg_w73vTe1 RXoEj Y}ʮL}H y#];}7}8~O4cfXφ]!g\>dKЛ?%9lx\tc!F Mن9~≌ݵ2OVsFmF"-^V=3 wp;j;81@gAs' Y־,}QsݨaCF8 Oڞi=̮Z�Jaǡw|m=OcqҟQ9�48?Qtl7t<'ؒ<{?Fd˽:݆J~:ٕ�اKV®<|'a~#QiW"5K^ ~^Q\]U}eSdk,аliP+B 8c<}s9]yӿ[ )5[PYxҭNӹٛmSS') J<A�һMK�  ,@n7[?��7cƥ! ٟ03bDu�I]&:@PS'Q %'*rFFTnH|�yg1 d"3B.zW\<@*zbPTs-D Fz8!zF@4�Qh1q.C@ w!1DpOLBzVp W/!dׯ ÿPBBnj ^ g*^]3ĐF3Đ_=L\G!gqƭ^5i_άqF^5I ny#<Az3vJΈ8% Xcgz8(S rAf{ٻGd8.R!c9ǙuPd:83pL Bz7G%gqy:2KQ(LR8,F=oތ^`?4Xz^sAH~'2XAH}F"#RyAK 2ZceV9X׀赼SQpJb*X9X!vC፬*,t$ zj _Ь V<$bÍ|lQ%OsE,xW'ݵt$k ̒=ʈ2ѩ;dn8s_[;ɁĹ+wf [wr@ [`ê69 %ZeAܿ]?A}ӵYSfgT2y WÉʶOo+'h>Zrҩi/hK/~t2j̚W'k0:Y$,L"o)k"kpxٙ*$rkࠄ^<[y7TM\r,qRavT^i9Ī݉6 K i"£t; 09Ba" 3U׌B5Bî;bgKrϐwD_Y-NQT u-jpjgm'.*M ̰#z-jܛ+鉉<VZbӱj�m>xsv+Пf۞xVR WDSv8Tdϳ7;ÞY9T(PIR6!ص`s꜅5wMִq< ۹M삑060d C.oB͹@ֽQo4in 4ݻ ܖ:i@sZ4-w!XQ6 إ(,ip=n{egPN^Jܴan[IW~ 8VtK�xv/{Ei{D~+˲NMj=o3.\@(ܔ}tyJn +z޳V닸wV YdtIV j[(OC}S;>x0a% ^8 5�g;|!S QE8^( E@]N!DMsdO\ :0W#p%3VC`n2%oO1*=O+瞁\AТ9YY΃$f?Q40*Veర V-_J0^U0U>C\݃d0_e4´W1j9U:XUPU.%*asF(Tzqw*aS4Er-Ţ`KC9NC=ć-'*UFR�j,`D(}E!@ >A0ޓ0@OQMOa|fH't(@\&҅yؽ]�85JBӒԿL-IP:1=K]p֥aឱLԃєJF9Ie= .S2 &1TRa#c$u8]8t- uA&3 a#X']#@n.bwfhg.HElY6h`5pe9ƀDՏDtV1Qtޏцf 62P)# ĥXbZF # (A5NȵtG'z+P=;8a3K<ԝpbU!d@b =\l ˕5J!v;\~6VAh^}Es^5j,tC0٭.lergݽ{7t9Y�~lKgCf5F:־ܿv+yiu 6ca`vN\ul]d-櫓nR7u ;eg%f0EWu ^בXLȴ8.] l2\[.=?lWï-P%:).DnGMpC⻯pÏy.ʽ]VAsamX*@ޟkWk΂&޸8|x}D#WK}3G4Do{v}qޟiAc~оW܀w&J=!5 >s4z4Z^쪳V4"{qPnIO*/4) ䷘]a3`lwOBAɭy~45MRφdž +s[뗧 96vC c=*Xjʑy T5iPiA߃zK{gnەÇu.+?ٮ?].XU?JA '7;F@fNZ_!.oBC54Zj�7rt?D@OBv"O]G�FZ=?"�¢kB| Uq`kRRLG4.�ef<И:�UJ)kHh㬫J%EG`�G] OI%0i #g u&jeܟCV=bSo5\rB:u 粷"`Ps2@JSc2ջ`Pe;x1K.C �s!kv Ug@KÞTf" `у6Gݎ�*ԅфZ0PÇKfp7P 0.|w�Ԇ$1[ɩlT_r_Q `8ס͹e$^G!gv5a'jmzqj~ 'ɸ.q~P} !n!Bcy@$Σ/yj8_@|7Pĺ0ȋ/×#aun X-8T!dW8Ց媃V'y )tĕCie!/ (!G2pܳO'?곇;#R|{ne.d5jOKmHf~7wpA:AmGݖ%Oc2p{Yݽh۾%BtJTĔenF5:\:Qvj`M2ZWۿ5&q®Vh+#&J`7`RpNZa6 uB hIkPEnMCV#|*0%"<u�hRBn/::Ch̊1=,c]~"APuLϞ7qmDLb43\٤*Thu:eZAYApZ+!TVIu˴r\ٵs Z7? $qAQHI:'[T6;e`b5m8a0VΰcIΔmA&ꟍ M�htZUF4N%\oW" 9e>GQMY\<]SVL޹ol&4\TlrYsEBDbgD%3zӳ˜ӝ=l WNv ji/<Źww)2n/un'jݹXv?575`x|zb7<\GZӠݳf^wkZ)6b'JWJ5tiatW] NGtxBYoaiOVx=ώ0=4avpg\Tqt7:薵wA^kfC喵AM#'׬roֵY~gB LjTA!97˄3]u J]Vrj={*\k]/t{"27#԰d2+J_k&޽,bYӞ)jJ%{uK-Ln'N^'~4j7,h›Ldƥ0FV<>by?<\̐_ZA/k�Ԋ|l@^ЌN엻uF}8�Xre3 BWr?at2Ѷ `eZ9 ePZ//(85 [eQC�z FƪQaH䠌Ep@~*i[]U p]6N9(cZ> f}Pl95V91*-0ePQ2OQZ0_aĩPS-E2AIU0S?�RQCjs E_ȀPՉP]~I@ؙ1 < d�S즒mrd@&ȃϤ0>("1(KDuI%1,Q*FV8Af g=I$%0PRLTLDB'ҳ@m aQ"U1Q%CKL48IH_a[(RQCs(@�C!Nw FFCb"3-d4Pe&*U1~�DӹO||#}? gc$v^�BqITzGΧг|dWI6*= ?'_jQB.o"Bn|'*ǎB'x`Ǚg$@}ShCOYτ5\nUY^:D`_7}7lD=<Mj#R NCLsIŧWGAdZ= PmA7}vEؓ77n~ zcoq}sܹMR7}J;pspf():sV S|Xv )s.vm]`o1Ň4MB=2ƐrNp^ԲycPo2b:m^]DgH# \Ď{TY++r6CqL,Gs\JY:UTj?+FCAj?2*WN磔Xw~A>߸dԸ~gapyʮx_6s0Ն_Ql!E]A?|{uvJX?$fSg;Q^pg&[Wl4= GX5k}ǙPіHο5?jǫϸOxюӵfiڡTy8AA5%Ҭl`ȏU\:?3z?z L]s#A]KLU%ZsZ_"|+TS+i7muhtf}&)O~p$`&Tb<�5�ֵ(D|sMA�rJkE8ݪHAٺ8�BNo3�(S:t_ c5} K PT]2)U 0^an5H?1bjgĝ1R/G]%.xCM G#�2b2On`'�XE�^Tƨ+*]bdl1@d�$*=`,!gqءM�4T3C>Cv@sLPD>TƸAR Z҆f7 v2cI0($C@י\@$z8T_ MDgOt^=p*N/{$ĉ Ϋrp"ՙ?cyZ+Y\[{컁zRf"ATB":Π[Rgt3_$z%_z8/>YLRKR UgJe 5?3Jgl8i9}?Mo8LUŀjM"oo.q՞h ^Ad2Dc6,s[�`:.%1B␪xbCRƵyi wJ>(ч(X.eDh+o05YdTZ׵ .[)bqpah 9㕴fk4{].U t"dZ_Ï͎4ޗ4 V3QmX}KΤ4~tz` ?%;v&b:Iʅ+@ w;=mGWu3^MJbGI,:N;oTvcxܐF ikAjfga`lDO·a<j6�^]A%j[E_*5ǬhpvT T;BITD0Ad⺯> ۴ oMn Y =3vO}:!"!x-k]]BZirlin0})OUV�QPll~ωRb֌΄'<p<vzǚMvѩ'r/RxP;[Ow8<V[wb f0Łv7?YgLrЙUr%96u^DDJ^D1/<ٵ+]Em4'fô fȥ{Q�w#x3J;l�c̮k~rĵ۝8pL/Gd`zɎͣ5(ůP]f~>϶-9*eiAms;&Dg;goži_H.7l"-V] kwQ/o d+Y;sA0?߇?|*gC [3[!6ȫF?*aB%b\{�8Ώ0+tѶ[ 9KZh֌(Т:l!N`,7$V(`a U ֘aO)h'Е>>W\/fhe0BF0bnU#CT9sƦzm JF4D~jY zPS1E2UEg h?�Gu`xɃX/|`DqfapuPܕ;^Q"*NT/'{2=魣t\6sV'3t`$'�r "e�m^ƅ"LBgjL@Dqσ4X1R.72ܒ_b W`N60Rc *]w j ]n�.gfu$ t@RFiv0J@qD+Z+AJ\?ts�ЈȐF`uѭ:F/2lQO3+z3MPL"-#W| 16?{pia5�oȵ -vJEȈ;A:9Ob?O f1P X-OjqHQIջ#pV ӖiRv$uIlh*[D ,z<%ZMd%?xUtxӂJOK}#-ް:ԭjg&)0uKyͶN7>{Se nk!7r` =ۂl(- 3[.B5}~cTxQ<I p4G?A_rn;Pշ>GX!MKE@rՆ_}Gu (Vܮf<X-N=y EqI4Xn`gd&ӄ39wO:ro_s.k$@xR應+ιȊ)jw psvՐ_lbbnx!wh 䝭Y(G]%><T S{MZ?ݾסּAg,KDQKBxxFVBb+X;G(V4(**s7_v A!t'u.� 2-7T -}F2L>6>D^?**(h"ՊA&oӭei$lռQfY6%>h-,2{74tbZ%;Y|T=Lj|ݏwvj |>`S:ؿ:ٿʇ:[盽9TS m !7m 7߄94ݼ=PBlj|6DV @fއگt yYn1fdu5�Q #k (1r B>g�FH0 Y yY9RF@CuH@>zZF8@6u& ue1r}X�\ @(CL=H_F׉q\_c0 a=HBo#nG�Ġ}HA># t1"�Qn?#c ӿSman~\*s32C/"dɩ#Cqʙu5p&(qmL{ :(2+ȨnI ZgΗ0 )g'N@g:9NȀup{큌nLw03\/^"d*LV3!M^ͬ˒"S nh0iEFΙS@=y(S g S<, LRdg=]O;>-#(Ug,"ݖyڅ DmBcxTk(X MB]){<{M X,sFJkEmjn ;)T6nB Nq 93Z &s^\LEjRKwEbjKobR'҂-LP^ҕ94{]h1p ]!Q>KTI`",rʔSmX]۸AewG7GMΉs [ uÂQ6<L4~,%}MRܔu2DdZYJVq'mK8jr$VKg~&"XÚS>y!G DĐ2ԞNzOrXHqaQfRJ)Ln ƙͭtmv~jLaUBxJ͋®z`՚VpWDA2)9l^2ȆV�{@`s5P٘mX87hT!{i3Z:|璛d7A)َ] xN̹v #vϭ:?:VqPT'[ n4Va>7RnO@%^GPV*SXNߛJ7{c.!ڮAq)WnX̭/>{Ih"gAɺaףDڶgT 77eơ- 培wrs}ZV)NRg3ϋtNNk^c5Mkb9rM+w(j[ԲzBJ (߲U7g8ݲ:7v4n/p;Aiz( Kk١(t*~zCmv/qWwвW#{Vشe?攤?{}O shZ^hЬo yd+W�)$W~]HXW;I0~<pr07XR{40!Aۢ`iQ /*V;0D,~Fz-.Rd dQdl~b`ae?W{qw/7l`e"LzfgU? `Qէ4 R O `,1x;S/ؑEZ1ǎ~G`TwTWQ1|:2b?LCQ0,nd<XnT%B B|Z- P^x'qsz|1S=c71nwb0<L�C1 &~ץRC [֐,C*U0[p@`=aST1%Q0PS@H!4B Hć=Km$LxQ1NĆm3v>Ԯq XDC{JbAA&A@I(hPˉ!�0dz1PI# 0(�'d tn)Ox?ĩ ΌT' >;�y@Ǚn ^Lպ]CP{,#K>DG{pJش c AsAA,Iw*bh,c]e sadobZ?מDfyo=8-NG>"8%uȻ&΅[P7kmI|7Z\?ǷWp@%_ dcUH~f`<lB#Jbc,P_Γ~ TRH<,,g D+Xg{ ϦZ[x<>a.ތ9H9q:A̫"kDZ ϐ8/;):/6lw]xaہkF~zbϦ dFos l@~$oZgpm'h4UD4*ESwQ{|0 # n^!8&y>v@~_ ʹEd(~Ro&boؿ.RN5VM(D @G|ly�v3g{R\EozI_hsEfզ8DUTҶOWܾcI){kl\'?|zzkm4-?;~F(]ln݂W;ye#„cg0@%V@Y`Vv;.wO'?i`zW/ ׅi?jo!gԵ.7P\‡ g. ;�G@4V%d/5Rí`�xZUT; (`t}2Xjs{�+EJg<ty]f>�@jȧ fȣvu2cpӋ6Tyg4'F1#50[.�E>2Ɂ`u<a0r P`g¸ q�0j�,cŸ!LXE�# x0`2(\r� Cjc�pn __Aj@bU!B* !W*N{@Yet]'!gvw쩤Bś;$q㔳nbn_g<nDF?�Ua@ˑ9O=(2f56TY7ɱ.qV|�HՂ.v((?d&nX$Bv{5 |*!{ˉl1*gj(ƩuPש#Y,27Bzkcfʳ3=)KPliWS?수 O,?uZm("UeKG¦6w<ٜ}ϖ?&,yڳgCuJu;PaU/L٬k)p}x=Ul6vtLGc:5öé&UʩQ+ BwɉRu+ xo9D&E_zN%Io-,Cڋd~HQ[6͕_;*A ɒw)}+T@}89[sWrA<7~~'ޮc5nohy. ՝=AC>ݑ:9fa7^Jv?$,wsf*Jil 8-J\<Ͱ]b̴T4nZfcŌ~SyYӍˌF]Ҥߒeq9ebk ѭ644SJn[ʖuw/M3D8)mߝT`a.@1<%9-\lIq%:ٝ .}dzjj0rF8v0Y.52HA)du/(}`}i6Ĵ ^I_U캾USg�"F7Y13/^D';:?~RU[k:y;wۙkzzs#֭Xtu 3+WܺNqoz}"ށ73Z^_?Ntw []g{E~P}D\nL5DD e⾻6Sr dI k{D-ՋA(U}ڜJs;nɷƭ[xTއ.,׺VGY`Ƨ.TJ6eԾlѱ (E6p g?[`7a+8wO:=ǀRȬu@oxtA{WBu4r}CoPφ~U/ZA/{!֫]�ތS�^�܅B̐]HnjՃSJg<,QB,~FjK([O Z?/ ?�9AjZù6ЉQ\g\~�b0+^;`WnAjV ׊AHV a6 Z>26>c`bHr|29�7U8�|�SDX*܅%UMRſ=pQpNc0@>>P J,C ZڹoHdIG2+:oFh. 7Ql1bq Z/L{eoc98L}`U5kW�3 J°J)5bH|ܤˌ1ӥ@XS?()C(򪃋uѥ@ng} =SJ܈QnbaX#]| 1Q#@3*~2�VTzPz0<PøDm]9HD\B*'bС%vU! U2a\b (n!:0;A z F  @o?||#_ϟ^O@˛6b(L/5֤hh>)zf+>ڡ}tP|t#+<׋x^2>3P'^J #"[z\^}~ᡴniAD_ ln[n[V1JoVorIȨDq~VZ;)t:tQ!^QzF`# /:~?C fϗ-]V@^egK0åiW&U]X}2GUl~œ_aPWgF]Z+:~x曗W>˦/iRYYkAN BGŪK"Fvo>NJCM` [7E_6ܿȔj/d3ʰ<\ÊvmpʪT9 Ejޟ;=6x^+Ua ;:?6p:HR*vma,.o o\JdwC; %_hdB!!oTr^pVYYVy{JgZFl"M`Œd7C rm0yV2~xDeyW">7φcS~#ƻ!=Y^?Mg1 HFj=Sx$)4;,CݿңGB> u)aSf>mox뭽6,;q*9pF@\>&<icO'7O;>tpUfK4=Э0 BvHh ͜o nB&dz% 2= 1L<@^ĥ* K(VM4"`5gUU!pM*[byFJW�T:1Ā.`P:(OR;)%1^"3B&U-r�a$\2U*1Z!�S.J1(ßt!3QY 3dGz`\& KHAӹD<$9�=CT4�6BKj(rh��cZ�T=יVݨ܇Đ_5U_z(]yu$~} iW3N/$wCHƯ$vɷ N홯$:84Ѐ< .s0_ׂDY3'&ӓ}wd@"|j@⫋ 'g!,NpuXrfKUqn\? Vզ>DY@!3vPJԘ?eI&?YOx?l:8ߌ\�Byz*>y7xɥ|NHWB.TҧX"0h ߵG>8rZfqcA2݀"^uP>%N\K;:;zRwEԍspf˄L-t'AIP>{DGPkbp6 ou[ ]+ͳʝ-FwRc"Gpvsˡ,u[ezVy8[ |&k%M4*}'<͍Pk rTJ{yVk{jTw{1;b﮸ SչDTZM*Jjo8saww'=Cg<jT:s"0&;Y+;egv6HCstӳa 3оžʡn3 3Ep_Pjs9.J[Bn(JeMDgCoVSKM tJ&<:H r~WP(9#ZJ92WPQ=5C7]xs7f@%SlNAU)]~hA5e*^@<i}j:KVN:QG%A@ !^)8fIBk9+غwN}I ã;5Yh_eKpC\e m͇c#w(<9" zJP~" J"턨V(Hn5[P:Qr֑8-lcp2+mKZ'Zi/w\#V -Nڴ%%voN,Cu+RC\JuK Wr2 0wZ]D/сLNҳ3fkqu;]@4]Qc$DJIvI"Dp$@? |>?Sφ|ϐ_? \XɈa�W6?`.  ;(~?%3JWC *NHs�~ňgЭz+̾L;�KDzZ: {B?8H~=gd,s -B UW; {UxWN�]FV-8ic|0Ɂr^^XEvBUCS�؋E c~Ph7N3Lz@L[A60T;HM[�~qA}�Q9 1&o0b(c?9;XV@=uSQesd`'zFn"Ub&XcCFgP111ɯ?K%T`K%eB+�WiP<J&ARrbBEN*aT4<-aĉT`E ƈ(+^m( ` գ0Q+Ru$1�&j 5^c(0"EFA-IʥuPdtd"!@">Qx1y0CfPd`qQC ݃1ĥx~POb?OZ a;?2&Ş#ng;^\gnL4kGԭp|2GC[fvѱø m)Rٳߟ?FW;֍gNna;.y53{mmp:b y6`mHӜ\hPkVG[1n|5}o@ՌĽcrJoljy@p ـ1lA!R%L(HߎĆXɀ+72PXnUH?Ԇp_vyyxny}jfn ,<A;na3|SȻ ߯ 'bmNWt * v2ND%Ƚ\1#| ȇR#-P9Uqu>~ԝF`gފ=S#O4:P'"nc4i, z\yP|-5vgb'5^d�-JإW{Wgk\bUߨtQ'V+8^IQ'㳳˝چl#BI@i[;Y_ TnǛZe+I{\kEϟa-33}BMd3\k|YۯQ=~[5<QvNW'Q3!j.7MsKvr!?O $И!͆j^ 씋 Dw W% eZ=7!ylĥ%@fjbDTp DCLI@^K�ivFXwDJO##YEUtjTfz%j@:1n6�s7ҹ>YjMGQR !PE!@do!ɵ�r7ݞ)�WB!@PB!uP@a埡60D'&A|�troP@!ݠ2,Noe*M4Krv_ ^/?$:McQgd2 dz9Q|Jsh;q|!YyCypBKpKç}Nz<8ڀ,* ޒ�^Y^{ } NS/Qӫi NG/лB{m@ک3N7u4KIJȒ&?ɯoGP{Ȱ\^Zzo"rFEnΔaz–ARQX.[brxi,sAvd=Yk Z0E2XC¾A'qrJЮbtb٣+TwRٲ U2v*�lRge,+MpVKs'Yz m@fRr)K )R. 0 {�U .lv968=~J 86uLl3|<5>pS] ljclqܜne^)i389מupaE -hlxiWE~qyvӾliPQ""knv~ od =߭ >q`n޴j!]-C}Mw96Ij6bF9P̻PyNƻ-Fòf�1y:Q,Q_\MjWh$|f,bR ;~#!FKͮVzLZ%|6wiis;�l4 x/g=!߳UK:;jq+^Oe]ZщvI4\Z|.bdʴZxtknPPݝJ2b;!MZʩRAFz糠6M+Nǻ9+HqJnV`B9KT>4bg6t9B.[+~a")Zl2RN'ܱid^ݸ۽:j-"4.[uC@ꅤY+Y'‚v!s—jj5wwI l]7N2]%2PL:^MukM$xkFDٵMe. SlMNg{JOfZ5`M7ͭ1\r{7PV{͢EtV,>G+gIq?K22~b)¡E�н_?"=p0+#wqFa."t%3:7lzG8x\` {0hP rl oQ.d\-v�Hjāзwf1hV5eub| bPX29)w"8hWT#S{[8 l5 ;0U`X= ƮB?UU %(DEAE"qai`)3e|yK}zq %UR.8 xy�-jDV|(C�C0"TuCA?w0I@�Kc{J餣I?3.b&ʸMn%6r$<v.{1G~}.32ິ?E.2XAFU?C> TxI~8]n>kY.1< SFEyVp�h0qXR&Ht I5"c:x&]&% Uݰٓx*̢+\JC׌KX$Apsh`2AFfxZ+AB| ??iB-+RsV<xY+HBKգlɤ}]pl)-|ˈڱ TA;yR}, r恗Vvw]JuN<IZI +ȣ*  "C**SH> UY" m KQF`cCzٰ$F̰d# +5M. ǽ\o* /OD׿FJ|`h H57.~UFuWӒ+D-Y&o_!Uye&a:־%8,,~uL=`#957*󙜛b||  o"t|pjWl`w=HiWVDu?[l??B4rʠe hg=mxXV,z7lm 0l \5@dy哆; cxu2FKMDU 7O46sԬG&+"Wg>q :^}~C6d[IXjWWK=ɎKBu 5j7+ɐMi}$?C8%ūF<x{rM`@BI^SP.JaMl_SmX/ɹ%ڤL D]WzQzKՉ'T}ohkA)6^?i7O;>Y@#B|j?*V Q ĉHn(et; �ҲѮ4PL` iBH�R ԥtboÐiH71T"at74�F�i3QO<׵G\ m. @P=]eȢ.*C Q.*@W�I.  Ck❊�9t0Ԙ"`q Z\:$@K.Aa??j06]Un6CڥKev#W_N=_$)}N/RRKm@~PSL nr jkqr sJ=TÊǚq|5 eT#z85ց)@e R_ ^{ . ^ASYD`9U=HQ/1R[^FCSPtzjsʩTjK#]Z^ H)//#)d{ǟy2ʟ|C/\mݹ>C^O--5Sڬ$"a+C/mv}}ZvZ-qN \Ћbٯՙpckp[l,OBGU_mU ݽNpv$rq҈Ȏs:מjeìP S^TX<:cI%k|Ͱq+Xo]昈-/6mL}Lx&gÜ8IFxz E-[)ca'謚z2D QtnήJar#S5<PNy:xv b?ӏIH,+$oD8 m@Rh75csF/QÎ1ٸ1ݖܝGB2m麒UfmԳ=ϚB N1Tj:}BDDݒ%�jPvNsw(N4(]AőW(cuZA<V>j6<Dic"vֱmhdl` JPm"KSv]C뗹Nw}7̓@C*ޅI?X4m!V*YS 't+^!BЊdK ˹%A1Ollt{iԞJ֜]PAjq;Ò]?S5zm0ݭX1ӚNb/f/ni&d3+hVIe(+2Au_bl =ya _ޯ]z2L4g&GiH9C;wH^~.HSyXkƀc1w1S{Vʚ rrB^hmνkҍzci%;Y0qN{ vucƣ4^t*:nv%yܢ1~Ft12f`߇z7}`(gCY'CKO&&@¾fX/ ?ϟ ] مXXIA\ >*{�)dxP5DS}UZ8V K -Z0tV bp}%ҁz ŷ& @:" GiO>j WAVY�V=@cV18`U8(�?"yƥ"9TDFJg }Ɯ**by @=�/c<J< @rT ʷgxyXQ֫ax(q<Xe�.uܡ]%wyU&p'�Н+$vhrba*B(`v& 2U[2C0:KKt)8$Jp&-'v�REQ21M Sll"<xjpP{}iD0 MQ{n+1p Nֱ<Ps y]~Q#�ٌXOTQ'2~( 3q+�4Mf-]9kĮA 9Ӄ.Կ BЃ'D-|wAZ?> Ϛ'|�*·OݧSLhe__pƮ@�i*O%zWp-e֦z.D]p=HQ2^P e&,Z6mȮ]?7dx|lzi#$ذϻ5Dnn/3Եh9;w7x>n/2G@ChvTWnk4qk7cvE [=Ey&q ڀjv'ͣ  6k\ِ*?~` 5+.θ [>x 嗽I &U[wDg(h##| ]i2]-*ZGef2Zrϼ&2S^ P:j\f4Fݺk B_x5 O⿗<�n ZB'=yLqM_85ɇJwwL$zB#VUXvq:l"]͝O�k:P9ҁǝeN QYC1NYրlݶM1W)K2]|"(=zӃ>5uY|lF+5t$Ry6#y&bCBQ 9dg&++A1OJꧬnY~}=jC:v{uNz6RF` :?I`zBu Hj5(c8W}"zT?7!5} oJ`X=@O�}UvUxVgT&?]ЪgD1Tj�v3c)'j�6¬gPDe�4ԩ0H qO r�<MJ@:MSB8ئs jt0�bT�-w޻ 咲ȇ<s\f>�!۟0?QF �Ë �&]xx0fh"*b@SPJM.ウf>�U$e1R!3S29O1wD^oWRR]g'CY廃DywdxS1RwS˙vi9>^I!nE(ȩ gM:N9gz-H[<1ΒZ!=Nxg>H/L$w%*D >ނV[J`$!Pg M']~6q*JA1N>\85<ϟU'?xreDpTܽ$?#JxSgTW^+`n d| f.z~j"#Y6Z`}%' O^5``y`D\xe8G~[A5!l'=LZ,R2رgJ+B5ESbOɶVE'y$.ՙ̽MW҃';^,d'Ir؆ONp~@B@o^=v%FAbZ.+Z]feZvn6;MroCATc!:?A; o@Ěwˉ`PR<VTP#�\(ucv7/} I KZJVOke-OGPza)ixͨ]YtYLJgfOu+}ޭ՚qώە`~řY落vm:]Ax;UXHrWBiB,z˅];hpLD+5cX_14]SerK\z" #[ E^Gu V|akUj;}/tJSmb]չD+jEroZ_I-lxF<�u}>IRKx(+'bѻ}@8ml>^eF;vjl T锚]uk;~oPe zɘQnEFT1jaC'8{D6!0K3>JNݴ+_<NuӐbf߮!Z.M2<5) V(rVSpVJ0*d@ P [SmhkW~<״9wk=YhU땴Ox? u|>>V "=,FW_=fE _.ΰ^hΌE@^t Džh  3JЁa\.TZf0E [\AB>6KPKg$-g~ d0v>�a-_?ɗg[i�0V<�:s^q zйb?*)8VCxMuV8hUE@ SUS�v?T; Eڃ1|ӧ2wc�Y@B?PR3C2xT!F.J(e4d>` e\PD�. X(I6@5c%ӄ41900~S} 70bSqC5e=aT&Z丠Y% 40R} vYVŮ xJn (f:i$%JR ãH%JAbю1i`^B�=* c9�W8@42ʨX`Q"v^Zڊ>8E5�(zbLthH:"2* R8eq,ʝ;#ktuɵtIO[>C ֻ?'=Qv>7| <jZ'VGӏQc;OT!~2!;u^iGᗇ .[~3~e1 lDD]=>z.oأVV:psW(^ 58Ǐcf>;YĮ9wllp�wZ8.sx͡KA۝&Bfs5-mء۽/PfKw ^X|9;6/|7 8~oˈՉ;R}nJgdz'M;bs׸Zqs| ?#^xkj<@#0sذƂdy 38?&8Ȑ#V_g건<^?گyѧfm|O;Z8,>s OC3𺹈{1qvL%ms O0 J]|~ 6F{+N[T&֯o c:EGmWP 0cl7%ǻBzCOx{2ms^mjE| җ/h4,;Ne쌪OϡZ^k O؁ko 7O`$k6lJKLZi~JX 93M|ʊg!ϥv`o(>}`7̦"`5�Pc¯BRB60Z8pC rq.U 0Ե3]2,`%`:(0!,dG#�864ׯ�ˌx)Tm r:z^0FQ&!Pfm`6"$a�kcuqe q]>0fR I@oc+W0u CU5UŹ^>j¹T3G(x_bc_ !Wr rrK\]8ms q}?¤?8^Zo%Ηer!"Ε8nj@N|jë^jO众r]mnt2紗f w>P_@ݹ@^6;I8Z̐{^rN1c Sކ@ g!貞ߟv -\�`tjM7 eR.2fiEߔK;ʼnaQyGޖ / =_@XP\!rk1Q굯1 %^~^v)_8u$պ%m*�;u'q*o-2rQ9,ow;Do-1.׫%V^E9o1ݓaUG)\3j=L^:kSغ3a?2E˪cޕxg*;5O^*̢oGᒭb´ZqfNOVsxn?ȯܝCųVB+F[H6w1gSn͆ rZѬÀ86 gē{+'nY Bw")/۲լ|2axJϨ*ܭxneJ2oaE[TG::wwf D�oAfUV.\e" s[r^ [7*;d'6gk%-B4y<ZI^i`e^ \"&$eBn'Ћ3wwh4D)<"̥y[|yߊzdlSY1awg<zӴ(Gbi)<ؗ找ӧ=;31B)RD>]�g~MդHX쩮1Zu89jO^Xm-Nd^@VgvZ;08iu]^ Kgd Uҍf~ *!M`Gd['蒓n_ˍ{kE{eOk~3=+Br˜ɩIѳryv.Ei~ W $s6,{eN yhjJwf:jX׿=Rpuِ7[a8ǯڹ{g Ӝ vWA]n<nK9ui<:nMU(bNTd TGGrKFӪHбΕ9͒VK~gq_o?CeFWbh=A~н$3֫</Jy<0K/3Պ)]Do9?*Apgݒ~h[2^(@WKjI>$+7Y,Z4Dв1&ScI%?`,\Z+r[ʩ[lh/[ÊtCr58p\dz I/㰨,JRO;%)@3e4)% hDTL|ܨ�1)# Æz r 1Pd\>B2!LG쵥kjKzq/XG"GÙS2֤JLF2U2|IL'F0m`Q΁.32m9%GzJ' +Jҵx@-z0./ O,jRc!%U$U+2>de"RiA!ũ ?IЎs.C2lTh(IFe@8=8CVdLѝ2v݌N> ETT "P :2g+T"! );} BGo r~:4#>O ?$)>S\SP嫲ϛ Лg.WSȦ~/ȷ7l}C16hUJ?3#Кr N<|J;93# G�g"^++zLp[٦\6*JNuV0F%|g=׉:ÕֿQ|lٯ W#{vaΓ럧b^|j =7oq묎Ti]OXzQPESFIϏ xn(okp8N{DA؋jlޟT¢KOSOȒ:K:E@͍wN v%=;�{zGn`Iu3ll@H*?e1~˜۷@r~_˂TgHT̚[<zTBV36u6H{Smo < y7L?R$PW;psxޣ%"-V0Օ5vR՞#d@xFɐ3<V[k/YDV?tSDZ`+iln`|6t<xE93Yg>:^ tHfk~lht`(zSD*SLDW6Kd|T `o|IY~ح oZ63~|*w?~AW{s^\8?g:ijͧ3܄2>m`w?6ʓW] cF.A R䮄x"+N{? ) 3%uwvW P=&d45I?m8mH 4ѩIJ73S 6X!%]7 twh%* D?f޹~s7ߨЂ:0Շ1zŘ_.#pv3KU>QQ�s@<r5Cf?.AF 5`P}#u@k!߅Ẅ@m`T.5TWkUTv_CKDP^ /S! !B4r ,Kt*+ׯO N rq={;x]w#CnsX ['\ժf`ZPD)GϗQ@Հ(Y%G'hX2G ѯ{#0(1Q*8U_&k(gt\d[@KM8t>1Z[q+OVr\ZUe !�kʁ z_8мH 0K9&o' bjGhՖ<OxtLWq>*kTOdr4r^;U;zwF?(]}vd#DiHUҴQgt~; >?JԉBs X +,C˰ln_#9EҠB<]LM.XL{g"BJ2�iy ˫( ن|ZCfWdaI7Owޖ1my1Ӣ2l \v^Biڂ:iL;*r)jHJDr 1<T/Z׫qJsNY2 ;؅i҉6ħ ׾5H³x+ryj4m}/3KGG>G|'!:"7HKC[ی|,aζL]DNYA6 ܭbMF8ؘp;pp~/$S*mݮfwIV GD67B'z5gxmSv_cJTZ9ι)Ԯ$C Q=;ϔ::1N?صӒHls`#Q\mV / #BkǴb<v=3uؾ~͚IcHNTnJ;O8~wZh۩o:vBn TJDA^gpr(CӲS -I@mȦl2ޢ;{At ]ҔiMzͷ8NTjHovZ֨ @f"GqW[7/;շ݊h5' Ȏi.*ݣ&l{Ֆbᴗ̎\f@[kd n}&d ;9ւg8T,cAm*b':Y2`{m=-4Co_Yqlf9$Y_ *�Kz=c0% ^%]2#J3`<}\7+gJGgQ0mp0[*XԚq$} )dfVoeI?,UT\|[�o; `I>{!tA@"o9X+ gř怬 VAYeUh!UyTBrG%=詟�sA zDwK�+œ%HZ�삑Q?(@ CzX:<(Z{n`?TS r/¼$ZLM vr6Lt0]Rc9M' 50MՊLf/:H_aS!0pc`[@K,Z40RzfE5T@Ġ$!$2m3 P!0TJ˜{+�5DcQ\6*aQ1Lg8sz1\“)="밉~6"vNDYd?&(zj}5TnjJ 5c=$Թ3eNdU搃m|3?OOOZBIvv�zm?Xw{SYxc@o@OzߔMj?CIWcj #Z ~Te`N2hkB@y`OцRv篟XǷ+^Cwgf>god,ޯOa}N 0aGR UP{ bAAT@ C謞Y yx?>_m`G$o"�[ 'Nq&DH)p)sm]۾G(dӯv5Zr@ 0\ h<?+=xW>8d11qDV@lkĆѭ}fKByM>?w0X?7jeW:T> 9w]L3OCǙM>я㌛wv_lhlsy"<~/ ׺x5(n<wlRD} Hg;IsɊhɭ|NվAs];Ϯ*ZN:Oj/6t~ FG|<n~e4,8ޞL^ySZ/ z^ 7ޝFB�F,zvnCx>dU~%rVoT[[k8כоR>oy~1Pou?.bA~Lʩ_hOx?x5س'~JSOzjB3.IHg5Q~gJ=[B?ru`ɠ]H<L5 !j}@^އkuH2a p{&*%AA39]Hr!]>D #`"] !wL@FSG]tTz3H@6NA4w%\2eY/_}dBd2$]\2e*qi\ @׈Q\;I\'*&k e0DvĄ0Egdp}b0G$`?&vՀ!ҿB8B$ TzSD!N !"wAnd[Gڗ!NPZ BkEm?ŌJ T֫!WBkCfj[ JOAlCcZ]28ym6XU;?Ǩ!NĤDeTǞ>s^@y}1(-O b^u ۳~F7#ùHLGPdߘ)=a.?XNB6{9[m fkNGZ<]Mޝ1:{64k3wlQզqB ϼWBdz0\u˰2||MYKtiDu)B՝.fwP_\4j5B]S fgϳ#Eݩzô k1yǔElPlY瞰Ob]35Y}[5*:4lx9ӻB4Lܹ[@~քa;glaSzo|!+R*&; -V2ݮjSKGZA)֬t8ɌDMls&*N>ﯣ9lTˑ2};hڛ1[NIi/jivNQ-$όT!JfZ+Y5I=@ <v>Pts1ws K,nHѝﭠR>~K;dwH`]uq8Dn;Ck^oWI$Q_s:Y#:h".{G üy@,sFBNEc8u? ww7v8V߮= ⵽hfv'KwqOugKN>Pu'd5Xg^퐁db=RvPo VZ+;3x}PelN'U(Xsh}VbVxܜ~{r愤R(dMfvR~@t۫<tjTm/,Zgegp5 u>d}*go4q¨KuXΨ;B;9=>Zu ,/N\'{FtxVӬ7PEPjvaԿ]( ýt$76ifdpһ%']Ҫյ!]9k;cf3_)�qE9~Ṷ ۚ>f3WKj5bAhɑ> ͺSe?X-:U"L* cþ*�sXv51W:[lE8vV9e5V:ǁW9.ش;WeS+ 9d*rטxLP5`#e Ƽ4¼xzfJ�&Ed6)5ù76iq@;d(# EqXPF5W.=rTҫz#*_X|tv1}d~MTFȌ3 ÙLT8&q8LuG `2Byx;ȥ(c}XqJǘtjaqb? mG-S*ɇBsM>&_aFqDsFU2Ψsjs�0I]@.rSYdtT1>f<27JL#*,d yCdu<CA[YB 2RU98*#: +?e'?_oOՉ'Ȍ󌮗T4[vo7͙'f<yx Lf'ݧ~=ϫ 3uMiԐҼv> 9w'.Vm2)TTpU*t۽+R&cvFFͮ=Q;Q <&+!^!MZ)l*(E(%x7 Ni'dQs2=J*2߹qךoALׂ_Ľ7TAsmXGZ ?fig ^ J2)1;Qhi{ZvRvEAgdNvrvmչOXk)#nO3j/J" v~ٵU1VB"l6w.5.?kV%VX|;wp_8}{ʙ.q%qouu-׏uˋYGaO CnB`vН3R=c'"$synJ NVB$_5vګ~ЯhCڒRKM"O\ sg]sPB矫.x^71=1�@l| *΅e׺]zfqzLFv:ɎlVql׭];}X l`+ZJFVn$w .Bخ&%N;rԖqS_?AG") )-H/w`rpƩdTP�VS iR6? 6%55os[U:v&^5 Ԟ91I} #Tf'u`h`ZjQAI5|.C Dt,P1iȽ0YOa8M-�7]}j/S]|X#w)6G XsntHL'W6"3a i:M<N1q4^A M|�=iQJ M?UĀT6p[ M}� ME4  ]l . Xj�Zc!1!Bi _$Wbbi?\DqupK8qNnv?\d 7vawWUa^BO 8a 8|I.&_ !ׯ ,8Dᯚ_q{5 h m28m**B`{}!pBޮh_&צ%D>|qjU(@U;(L[ I c^dBCq?~~~7vw�������������fio-2.1.3/trim.c������������������������������������������������������������������������������������0000664�0000000�0000000�00000003414�12220322320�0013377�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * TRIM/DISCARD support */ #include <unistd.h> #include <fcntl.h> #include <string.h> #include <assert.h> #include <pthread.h> #include "fio.h" #include "trim.h" #ifdef FIO_HAVE_TRIM int get_next_trim(struct thread_data *td, struct io_u *io_u) { struct io_piece *ipo; /* * this io_u is from a requeue, we already filled the offsets */ if (io_u->file) return 0; if (flist_empty(&td->trim_list)) return 1; assert(td->trim_entries); ipo = flist_entry(td->trim_list.next, struct io_piece, trim_list); remove_trim_entry(td, ipo); io_u->offset = ipo->offset; io_u->buflen = ipo->len; io_u->file = ipo->file; /* * If not verifying that trimmed ranges return zeroed data, * remove this from the to-read verify lists */ if (!td->o.trim_zero) { if (ipo->flags & IP_F_ONLIST) flist_del(&ipo->list); else { assert(ipo->flags & IP_F_ONRB); rb_erase(&ipo->rb_node, &td->io_hist_tree); } td->io_hist_len--; free(ipo); } else ipo->flags |= IP_F_TRIMMED; if (!fio_file_open(io_u->file)) { int r = td_io_open_file(td, io_u->file); if (r) { dprint(FD_VERIFY, "failed file %s open\n", io_u->file->file_name); return 1; } } get_file(io_u->file); assert(fio_file_open(io_u->file)); io_u->ddir = DDIR_TRIM; io_u->xfer_buf = NULL; io_u->xfer_buflen = io_u->buflen; dprint(FD_VERIFY, "get_next_trim: ret io_u %p\n", io_u); return 0; } int io_u_should_trim(struct thread_data *td, struct io_u *io_u) { unsigned long long val; unsigned long r; if (!td->o.trim_percentage) return 0; if (td->o.use_os_rand) { r = os_random_long(&td->trim_state); val = (OS_RAND_MAX / 100ULL); } else { r = __rand(&td->__trim_state); val = (FRAND_MAX / 100ULL); } val *= (unsigned long long) td->o.trim_percentage; return r <= val; } #endif ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������fio-2.1.3/trim.h������������������������������������������������������������������������������������0000664�0000000�0000000�00000001435�12220322320�0013405�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef FIO_TRIM_H #define FIO_TRIM_H #include "fio.h" #ifdef FIO_HAVE_TRIM extern int __must_check get_next_trim(struct thread_data *td, struct io_u *io_u); extern int io_u_should_trim(struct thread_data *td, struct io_u *io_u); /* * Determine whether a given io_u should be logged for verify or * for discard */ static inline void remove_trim_entry(struct thread_data *td, struct io_piece *ipo) { if (!flist_empty(&ipo->trim_list)) { flist_del_init(&ipo->trim_list); td->trim_entries--; } } #else static inline int get_next_trim(struct thread_data *td, struct io_u *io_u) { return 1; } static inline int io_u_should_trim(struct thread_data *td, struct io_u *io_u) { return 0; } static inline void remove_trim_entry(struct thread_data *td, struct io_piece *ipo) { } #endif #endif �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������fio-2.1.3/verify.c����������������������������������������������������������������������������������0000664�0000000�0000000�00000062424�12220322320�0013736�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * IO verification helpers */ #include <unistd.h> #include <fcntl.h> #include <string.h> #include <assert.h> #include <pthread.h> #include <libgen.h> #include "fio.h" #include "verify.h" #include "trim.h" #include "lib/rand.h" #include "lib/hweight.h" #include "crc/md5.h" #include "crc/crc64.h" #include "crc/crc32.h" #include "crc/crc32c.h" #include "crc/crc16.h" #include "crc/crc7.h" #include "crc/sha256.h" #include "crc/sha512.h" #include "crc/sha1.h" static void populate_hdr(struct thread_data *td, struct io_u *io_u, struct verify_header *hdr, unsigned int header_num, unsigned int header_len); void fill_pattern(struct thread_data *td, void *p, unsigned int len, struct io_u *io_u, unsigned long seed, int use_seed) { switch (td->o.verify_pattern_bytes) { case 0: dprint(FD_VERIFY, "fill random bytes len=%u\n", len); if (use_seed) __fill_random_buf(p, len, seed); else io_u->rand_seed = fill_random_buf(&td->buf_state, p, len); break; case 1: if (io_u->buf_filled_len >= len) { dprint(FD_VERIFY, "using already filled verify pattern b=0 len=%u\n", len); return; } dprint(FD_VERIFY, "fill verify pattern b=0 len=%u\n", len); memset(p, td->o.verify_pattern[0], len); io_u->buf_filled_len = len; break; default: { unsigned int i = 0, size = 0; unsigned char *b = p; if (io_u->buf_filled_len >= len) { dprint(FD_VERIFY, "using already filled verify pattern b=%d len=%u\n", td->o.verify_pattern_bytes, len); return; } dprint(FD_VERIFY, "fill verify pattern b=%d len=%u\n", td->o.verify_pattern_bytes, len); while (i < len) { size = td->o.verify_pattern_bytes; if (size > (len - i)) size = len - i; memcpy(b+i, td->o.verify_pattern, size); i += size; } io_u->buf_filled_len = len; break; } } } static unsigned int get_hdr_inc(struct thread_data *td, struct io_u *io_u) { unsigned int hdr_inc; hdr_inc = io_u->buflen; if (td->o.verify_interval && td->o.verify_interval <= io_u->buflen) hdr_inc = td->o.verify_interval; return hdr_inc; } static void fill_pattern_headers(struct thread_data *td, struct io_u *io_u, unsigned long seed, int use_seed) { unsigned int hdr_inc, header_num; struct verify_header *hdr; void *p = io_u->buf; fill_pattern(td, p, io_u->buflen, io_u, seed, use_seed); hdr_inc = get_hdr_inc(td, io_u); header_num = 0; for (; p < io_u->buf + io_u->buflen; p += hdr_inc) { hdr = p; populate_hdr(td, io_u, hdr, header_num, hdr_inc); header_num++; } } static void memswp(void *buf1, void *buf2, unsigned int len) { char swap[200]; assert(len <= sizeof(swap)); memcpy(&swap, buf1, len); memcpy(buf1, buf2, len); memcpy(buf2, &swap, len); } static void hexdump(void *buffer, int len) { unsigned char *p = buffer; int i; for (i = 0; i < len; i++) log_err("%02x", p[i]); log_err("\n"); } /* * Prepare for seperation of verify_header and checksum header */ static inline unsigned int __hdr_size(int verify_type) { unsigned int len = 0; switch (verify_type) { case VERIFY_NONE: case VERIFY_NULL: len = 0; break; case VERIFY_MD5: len = sizeof(struct vhdr_md5); break; case VERIFY_CRC64: len = sizeof(struct vhdr_crc64); break; case VERIFY_CRC32C: case VERIFY_CRC32: case VERIFY_CRC32C_INTEL: len = sizeof(struct vhdr_crc32); break; case VERIFY_CRC16: len = sizeof(struct vhdr_crc16); break; case VERIFY_CRC7: len = sizeof(struct vhdr_crc7); break; case VERIFY_SHA256: len = sizeof(struct vhdr_sha256); break; case VERIFY_SHA512: len = sizeof(struct vhdr_sha512); break; case VERIFY_META: len = sizeof(struct vhdr_meta); break; case VERIFY_SHA1: len = sizeof(struct vhdr_sha1); break; case VERIFY_PATTERN: len = 0; break; default: log_err("fio: unknown verify header!\n"); assert(0); } return len + sizeof(struct verify_header); } static inline unsigned int hdr_size(struct verify_header *hdr) { return __hdr_size(hdr->verify_type); } static void *hdr_priv(struct verify_header *hdr) { void *priv = hdr; return priv + sizeof(struct verify_header); } /* * Verify container, pass info to verify handlers and allow them to * pass info back in case of error */ struct vcont { /* * Input */ struct io_u *io_u; unsigned int hdr_num; struct thread_data *td; /* * Output, only valid in case of error */ const char *name; void *good_crc; void *bad_crc; unsigned int crc_len; }; static void dump_buf(char *buf, unsigned int len, unsigned long long offset, const char *type, struct fio_file *f) { char *ptr, fname[256]; int ret, fd; ptr = strdup(f->file_name); strcpy(fname, basename(ptr)); sprintf(fname + strlen(fname), ".%llu.%s", offset, type); fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0644); if (fd < 0) { perror("open verify buf file"); return; } while (len) { ret = write(fd, buf, len); if (!ret) break; else if (ret < 0) { perror("write verify buf file"); break; } len -= ret; buf += ret; } close(fd); log_err(" %s data dumped as %s\n", type, fname); free(ptr); } /* * Dump the contents of the read block and re-generate the correct data * and dump that too. */ static void dump_verify_buffers(struct verify_header *hdr, struct vcont *vc) { struct thread_data *td = vc->td; struct io_u *io_u = vc->io_u; unsigned long hdr_offset; struct io_u dummy; void *buf; if (!td->o.verify_dump) return; /* * Dump the contents we just read off disk */ hdr_offset = vc->hdr_num * hdr->len; dump_buf(io_u->buf + hdr_offset, hdr->len, io_u->offset + hdr_offset, "received", vc->io_u->file); /* * Allocate a new buf and re-generate the original data */ buf = malloc(io_u->buflen); dummy = *io_u; dummy.buf = buf; dummy.rand_seed = hdr->rand_seed; dummy.buf_filled_len = 0; dummy.buflen = io_u->buflen; fill_pattern_headers(td, &dummy, hdr->rand_seed, 1); dump_buf(buf + hdr_offset, hdr->len, io_u->offset + hdr_offset, "expected", vc->io_u->file); free(buf); } static void log_verify_failure(struct verify_header *hdr, struct vcont *vc) { unsigned long long offset; offset = vc->io_u->offset; offset += vc->hdr_num * hdr->len; log_err("%.8s: verify failed at file %s offset %llu, length %u\n", vc->name, vc->io_u->file->file_name, offset, hdr->len); if (vc->good_crc && vc->bad_crc) { log_err(" Expected CRC: "); hexdump(vc->good_crc, vc->crc_len); log_err(" Received CRC: "); hexdump(vc->bad_crc, vc->crc_len); } dump_verify_buffers(hdr, vc); } /* * Return data area 'header_num' */ static inline void *io_u_verify_off(struct verify_header *hdr, struct vcont *vc) { return vc->io_u->buf + vc->hdr_num * hdr->len + hdr_size(hdr); } static int verify_io_u_pattern(struct verify_header *hdr, struct vcont *vc) { struct thread_data *td = vc->td; struct io_u *io_u = vc->io_u; char *buf, *pattern; unsigned int header_size = __hdr_size(td->o.verify); unsigned int len, mod, i, size, pattern_size; pattern = td->o.verify_pattern; pattern_size = td->o.verify_pattern_bytes; if (pattern_size <= 1) pattern_size = MAX_PATTERN_SIZE; buf = (void *) hdr + header_size; len = get_hdr_inc(td, io_u) - header_size; mod = header_size % pattern_size; for (i = 0; i < len; i += size) { size = pattern_size - mod; if (size > (len - i)) size = len - i; if (memcmp(buf + i, pattern + mod, size)) /* Let the slow compare find the first mismatch byte. */ break; mod = 0; } for (; i < len; i++) { if (buf[i] != pattern[mod]) { unsigned int bits; bits = hweight8(buf[i] ^ pattern[mod]); log_err("fio: got pattern %x, wanted %x. Bad bits %d\n", buf[i], pattern[mod], bits); log_err("fio: bad pattern block offset %u\n", i); dump_verify_buffers(hdr, vc); return EILSEQ; } mod++; if (mod == td->o.verify_pattern_bytes) mod = 0; } return 0; } static int verify_io_u_meta(struct verify_header *hdr, struct vcont *vc) { struct thread_data *td = vc->td; struct vhdr_meta *vh = hdr_priv(hdr); struct io_u *io_u = vc->io_u; int ret = EILSEQ; dprint(FD_VERIFY, "meta verify io_u %p, len %u\n", io_u, hdr->len); if (vh->offset == io_u->offset + vc->hdr_num * td->o.verify_interval) ret = 0; if (td->o.verify_pattern_bytes) ret |= verify_io_u_pattern(hdr, vc); if (!ret) return 0; vc->name = "meta"; log_verify_failure(hdr, vc); return ret; } static int verify_io_u_sha512(struct verify_header *hdr, struct vcont *vc) { void *p = io_u_verify_off(hdr, vc); struct vhdr_sha512 *vh = hdr_priv(hdr); uint8_t sha512[128]; struct fio_sha512_ctx sha512_ctx = { .buf = sha512, }; dprint(FD_VERIFY, "sha512 verify io_u %p, len %u\n", vc->io_u, hdr->len); fio_sha512_init(&sha512_ctx); fio_sha512_update(&sha512_ctx, p, hdr->len - hdr_size(hdr)); if (!memcmp(vh->sha512, sha512_ctx.buf, sizeof(sha512))) return 0; vc->name = "sha512"; vc->good_crc = vh->sha512; vc->bad_crc = sha512_ctx.buf; vc->crc_len = sizeof(vh->sha512); log_verify_failure(hdr, vc); return EILSEQ; } static int verify_io_u_sha256(struct verify_header *hdr, struct vcont *vc) { void *p = io_u_verify_off(hdr, vc); struct vhdr_sha256 *vh = hdr_priv(hdr); uint8_t sha256[64]; struct fio_sha256_ctx sha256_ctx = { .buf = sha256, }; dprint(FD_VERIFY, "sha256 verify io_u %p, len %u\n", vc->io_u, hdr->len); fio_sha256_init(&sha256_ctx); fio_sha256_update(&sha256_ctx, p, hdr->len - hdr_size(hdr)); if (!memcmp(vh->sha256, sha256_ctx.buf, sizeof(sha256))) return 0; vc->name = "sha256"; vc->good_crc = vh->sha256; vc->bad_crc = sha256_ctx.buf; vc->crc_len = sizeof(vh->sha256); log_verify_failure(hdr, vc); return EILSEQ; } static int verify_io_u_sha1(struct verify_header *hdr, struct vcont *vc) { void *p = io_u_verify_off(hdr, vc); struct vhdr_sha1 *vh = hdr_priv(hdr); uint32_t sha1[5]; struct fio_sha1_ctx sha1_ctx = { .H = sha1, }; dprint(FD_VERIFY, "sha1 verify io_u %p, len %u\n", vc->io_u, hdr->len); fio_sha1_init(&sha1_ctx); fio_sha1_update(&sha1_ctx, p, hdr->len - hdr_size(hdr)); if (!memcmp(vh->sha1, sha1_ctx.H, sizeof(sha1))) return 0; vc->name = "sha1"; vc->good_crc = vh->sha1; vc->bad_crc = sha1_ctx.H; vc->crc_len = sizeof(vh->sha1); log_verify_failure(hdr, vc); return EILSEQ; } static int verify_io_u_crc7(struct verify_header *hdr, struct vcont *vc) { void *p = io_u_verify_off(hdr, vc); struct vhdr_crc7 *vh = hdr_priv(hdr); unsigned char c; dprint(FD_VERIFY, "crc7 verify io_u %p, len %u\n", vc->io_u, hdr->len); c = fio_crc7(p, hdr->len - hdr_size(hdr)); if (c == vh->crc7) return 0; vc->name = "crc7"; vc->good_crc = &vh->crc7; vc->bad_crc = &c; vc->crc_len = 1; log_verify_failure(hdr, vc); return EILSEQ; } static int verify_io_u_crc16(struct verify_header *hdr, struct vcont *vc) { void *p = io_u_verify_off(hdr, vc); struct vhdr_crc16 *vh = hdr_priv(hdr); unsigned short c; dprint(FD_VERIFY, "crc16 verify io_u %p, len %u\n", vc->io_u, hdr->len); c = fio_crc16(p, hdr->len - hdr_size(hdr)); if (c == vh->crc16) return 0; vc->name = "crc16"; vc->good_crc = &vh->crc16; vc->bad_crc = &c; vc->crc_len = 2; log_verify_failure(hdr, vc); return EILSEQ; } static int verify_io_u_crc64(struct verify_header *hdr, struct vcont *vc) { void *p = io_u_verify_off(hdr, vc); struct vhdr_crc64 *vh = hdr_priv(hdr); unsigned long long c; dprint(FD_VERIFY, "crc64 verify io_u %p, len %u\n", vc->io_u, hdr->len); c = fio_crc64(p, hdr->len - hdr_size(hdr)); if (c == vh->crc64) return 0; vc->name = "crc64"; vc->good_crc = &vh->crc64; vc->bad_crc = &c; vc->crc_len = 8; log_verify_failure(hdr, vc); return EILSEQ; } static int verify_io_u_crc32(struct verify_header *hdr, struct vcont *vc) { void *p = io_u_verify_off(hdr, vc); struct vhdr_crc32 *vh = hdr_priv(hdr); uint32_t c; dprint(FD_VERIFY, "crc32 verify io_u %p, len %u\n", vc->io_u, hdr->len); c = fio_crc32(p, hdr->len - hdr_size(hdr)); if (c == vh->crc32) return 0; vc->name = "crc32"; vc->good_crc = &vh->crc32; vc->bad_crc = &c; vc->crc_len = 4; log_verify_failure(hdr, vc); return EILSEQ; } static int verify_io_u_crc32c(struct verify_header *hdr, struct vcont *vc) { void *p = io_u_verify_off(hdr, vc); struct vhdr_crc32 *vh = hdr_priv(hdr); uint32_t c; dprint(FD_VERIFY, "crc32c verify io_u %p, len %u\n", vc->io_u, hdr->len); c = fio_crc32c(p, hdr->len - hdr_size(hdr)); if (c == vh->crc32) return 0; vc->name = "crc32c"; vc->good_crc = &vh->crc32; vc->bad_crc = &c; vc->crc_len = 4; log_verify_failure(hdr, vc); return EILSEQ; } static int verify_io_u_md5(struct verify_header *hdr, struct vcont *vc) { void *p = io_u_verify_off(hdr, vc); struct vhdr_md5 *vh = hdr_priv(hdr); uint32_t hash[MD5_HASH_WORDS]; struct fio_md5_ctx md5_ctx = { .hash = hash, }; dprint(FD_VERIFY, "md5 verify io_u %p, len %u\n", vc->io_u, hdr->len); fio_md5_init(&md5_ctx); fio_md5_update(&md5_ctx, p, hdr->len - hdr_size(hdr)); if (!memcmp(vh->md5_digest, md5_ctx.hash, sizeof(hash))) return 0; vc->name = "md5"; vc->good_crc = vh->md5_digest; vc->bad_crc = md5_ctx.hash; vc->crc_len = sizeof(hash); log_verify_failure(hdr, vc); return EILSEQ; } /* * Push IO verification to a separate thread */ int verify_io_u_async(struct thread_data *td, struct io_u *io_u) { if (io_u->file) put_file_log(td, io_u->file); pthread_mutex_lock(&td->io_u_lock); if (io_u->flags & IO_U_F_IN_CUR_DEPTH) { td->cur_depth--; io_u->flags &= ~IO_U_F_IN_CUR_DEPTH; } flist_add_tail(&io_u->verify_list, &td->verify_list); io_u->flags |= IO_U_F_FREE_DEF; pthread_mutex_unlock(&td->io_u_lock); pthread_cond_signal(&td->verify_cond); return 0; } static int verify_trimmed_io_u(struct thread_data *td, struct io_u *io_u) { static char zero_buf[1024]; unsigned int this_len, len; int ret = 0; void *p; if (!td->o.trim_zero) return 0; len = io_u->buflen; p = io_u->buf; do { this_len = sizeof(zero_buf); if (this_len > len) this_len = len; if (memcmp(p, zero_buf, this_len)) { ret = EILSEQ; break; } len -= this_len; p += this_len; } while (len); if (!ret) return 0; log_err("trim: verify failed at file %s offset %llu, length %lu" ", block offset %lu\n", io_u->file->file_name, io_u->offset, io_u->buflen, (unsigned long) (p - io_u->buf)); return ret; } static int verify_header(struct io_u *io_u, struct verify_header *hdr) { void *p = hdr; uint32_t crc; if (hdr->magic != FIO_HDR_MAGIC) return 0; if (hdr->len > io_u->buflen) { log_err("fio: verify header exceeds buffer length (%u > %lu)\n", hdr->len, io_u->buflen); return 0; } crc = fio_crc32c(p, offsetof(struct verify_header, crc32)); if (crc == hdr->crc32) return 1; log_err("fio: verify header crc %x, calculated %x\n", hdr->crc32, crc); return 0; } int verify_io_u(struct thread_data *td, struct io_u *io_u) { struct verify_header *hdr; unsigned int header_size, hdr_inc, hdr_num = 0; void *p; int ret; if (td->o.verify == VERIFY_NULL || io_u->ddir != DDIR_READ) return 0; if (io_u->flags & IO_U_F_TRIMMED) { ret = verify_trimmed_io_u(td, io_u); goto done; } hdr_inc = get_hdr_inc(td, io_u); ret = 0; for (p = io_u->buf; p < io_u->buf + io_u->buflen; p += hdr_inc, hdr_num++) { struct vcont vc = { .io_u = io_u, .hdr_num = hdr_num, .td = td, }; unsigned int verify_type; if (ret && td->o.verify_fatal) break; header_size = __hdr_size(td->o.verify); if (td->o.verify_offset) memswp(p, p + td->o.verify_offset, header_size); hdr = p; if (!verify_header(io_u, hdr)) { log_err("verify: bad magic header %x, wanted %x at " "file %s offset %llu, length %u\n", hdr->magic, FIO_HDR_MAGIC, io_u->file->file_name, io_u->offset + hdr_num * hdr->len, hdr->len); return EILSEQ; } if (td->o.verify != VERIFY_NONE) verify_type = td->o.verify; else verify_type = hdr->verify_type; switch (verify_type) { case VERIFY_MD5: ret = verify_io_u_md5(hdr, &vc); break; case VERIFY_CRC64: ret = verify_io_u_crc64(hdr, &vc); break; case VERIFY_CRC32C: case VERIFY_CRC32C_INTEL: ret = verify_io_u_crc32c(hdr, &vc); break; case VERIFY_CRC32: ret = verify_io_u_crc32(hdr, &vc); break; case VERIFY_CRC16: ret = verify_io_u_crc16(hdr, &vc); break; case VERIFY_CRC7: ret = verify_io_u_crc7(hdr, &vc); break; case VERIFY_SHA256: ret = verify_io_u_sha256(hdr, &vc); break; case VERIFY_SHA512: ret = verify_io_u_sha512(hdr, &vc); break; case VERIFY_META: ret = verify_io_u_meta(hdr, &vc); break; case VERIFY_SHA1: ret = verify_io_u_sha1(hdr, &vc); break; case VERIFY_PATTERN: ret = verify_io_u_pattern(hdr, &vc); break; default: log_err("Bad verify type %u\n", hdr->verify_type); ret = EINVAL; } if (ret && verify_type != hdr->verify_type) log_err("fio: verify type mismatch (%u media, %u given)\n", hdr->verify_type, verify_type); } done: if (ret && td->o.verify_fatal) td->terminate = 1; return ret; } static void fill_meta(struct verify_header *hdr, struct thread_data *td, struct io_u *io_u, unsigned int header_num) { struct vhdr_meta *vh = hdr_priv(hdr); vh->thread = td->thread_number; vh->time_sec = io_u->start_time.tv_sec; vh->time_usec = io_u->start_time.tv_usec; vh->numberio = td->io_issues[DDIR_WRITE]; vh->offset = io_u->offset + header_num * td->o.verify_interval; } static void fill_sha512(struct verify_header *hdr, void *p, unsigned int len) { struct vhdr_sha512 *vh = hdr_priv(hdr); struct fio_sha512_ctx sha512_ctx = { .buf = vh->sha512, }; fio_sha512_init(&sha512_ctx); fio_sha512_update(&sha512_ctx, p, len); } static void fill_sha256(struct verify_header *hdr, void *p, unsigned int len) { struct vhdr_sha256 *vh = hdr_priv(hdr); struct fio_sha256_ctx sha256_ctx = { .buf = vh->sha256, }; fio_sha256_init(&sha256_ctx); fio_sha256_update(&sha256_ctx, p, len); } static void fill_sha1(struct verify_header *hdr, void *p, unsigned int len) { struct vhdr_sha1 *vh = hdr_priv(hdr); struct fio_sha1_ctx sha1_ctx = { .H = vh->sha1, }; fio_sha1_init(&sha1_ctx); fio_sha1_update(&sha1_ctx, p, len); } static void fill_crc7(struct verify_header *hdr, void *p, unsigned int len) { struct vhdr_crc7 *vh = hdr_priv(hdr); vh->crc7 = fio_crc7(p, len); } static void fill_crc16(struct verify_header *hdr, void *p, unsigned int len) { struct vhdr_crc16 *vh = hdr_priv(hdr); vh->crc16 = fio_crc16(p, len); } static void fill_crc32(struct verify_header *hdr, void *p, unsigned int len) { struct vhdr_crc32 *vh = hdr_priv(hdr); vh->crc32 = fio_crc32(p, len); } static void fill_crc32c(struct verify_header *hdr, void *p, unsigned int len) { struct vhdr_crc32 *vh = hdr_priv(hdr); vh->crc32 = fio_crc32c(p, len); } static void fill_crc64(struct verify_header *hdr, void *p, unsigned int len) { struct vhdr_crc64 *vh = hdr_priv(hdr); vh->crc64 = fio_crc64(p, len); } static void fill_md5(struct verify_header *hdr, void *p, unsigned int len) { struct vhdr_md5 *vh = hdr_priv(hdr); struct fio_md5_ctx md5_ctx = { .hash = (uint32_t *) vh->md5_digest, }; fio_md5_init(&md5_ctx); fio_md5_update(&md5_ctx, p, len); } static void populate_hdr(struct thread_data *td, struct io_u *io_u, struct verify_header *hdr, unsigned int header_num, unsigned int header_len) { unsigned int data_len; void *data, *p; p = (void *) hdr; hdr->magic = FIO_HDR_MAGIC; hdr->verify_type = td->o.verify; hdr->len = header_len; hdr->rand_seed = io_u->rand_seed; hdr->crc32 = fio_crc32c(p, offsetof(struct verify_header, crc32)); data_len = header_len - hdr_size(hdr); data = p + hdr_size(hdr); switch (td->o.verify) { case VERIFY_MD5: dprint(FD_VERIFY, "fill md5 io_u %p, len %u\n", io_u, hdr->len); fill_md5(hdr, data, data_len); break; case VERIFY_CRC64: dprint(FD_VERIFY, "fill crc64 io_u %p, len %u\n", io_u, hdr->len); fill_crc64(hdr, data, data_len); break; case VERIFY_CRC32C: case VERIFY_CRC32C_INTEL: dprint(FD_VERIFY, "fill crc32c io_u %p, len %u\n", io_u, hdr->len); fill_crc32c(hdr, data, data_len); break; case VERIFY_CRC32: dprint(FD_VERIFY, "fill crc32 io_u %p, len %u\n", io_u, hdr->len); fill_crc32(hdr, data, data_len); break; case VERIFY_CRC16: dprint(FD_VERIFY, "fill crc16 io_u %p, len %u\n", io_u, hdr->len); fill_crc16(hdr, data, data_len); break; case VERIFY_CRC7: dprint(FD_VERIFY, "fill crc7 io_u %p, len %u\n", io_u, hdr->len); fill_crc7(hdr, data, data_len); break; case VERIFY_SHA256: dprint(FD_VERIFY, "fill sha256 io_u %p, len %u\n", io_u, hdr->len); fill_sha256(hdr, data, data_len); break; case VERIFY_SHA512: dprint(FD_VERIFY, "fill sha512 io_u %p, len %u\n", io_u, hdr->len); fill_sha512(hdr, data, data_len); break; case VERIFY_META: dprint(FD_VERIFY, "fill meta io_u %p, len %u\n", io_u, hdr->len); fill_meta(hdr, td, io_u, header_num); break; case VERIFY_SHA1: dprint(FD_VERIFY, "fill sha1 io_u %p, len %u\n", io_u, hdr->len); fill_sha1(hdr, data, data_len); break; case VERIFY_PATTERN: /* nothing to do here */ break; default: log_err("fio: bad verify type: %d\n", td->o.verify); assert(0); } if (td->o.verify_offset) memswp(p, p + td->o.verify_offset, hdr_size(hdr)); } /* * fill body of io_u->buf with random data and add a header with the * checksum of choice */ void populate_verify_io_u(struct thread_data *td, struct io_u *io_u) { if (td->o.verify == VERIFY_NULL) return; fill_pattern_headers(td, io_u, 0, 0); } int get_next_verify(struct thread_data *td, struct io_u *io_u) { struct io_piece *ipo = NULL; /* * this io_u is from a requeue, we already filled the offsets */ if (io_u->file) return 0; if (!RB_EMPTY_ROOT(&td->io_hist_tree)) { struct rb_node *n = rb_first(&td->io_hist_tree); ipo = rb_entry(n, struct io_piece, rb_node); rb_erase(n, &td->io_hist_tree); assert(ipo->flags & IP_F_ONRB); ipo->flags &= ~IP_F_ONRB; } else if (!flist_empty(&td->io_hist_list)) { ipo = flist_entry(td->io_hist_list.next, struct io_piece, list); flist_del(&ipo->list); assert(ipo->flags & IP_F_ONLIST); ipo->flags &= ~IP_F_ONLIST; } if (ipo) { td->io_hist_len--; io_u->offset = ipo->offset; io_u->buflen = ipo->len; io_u->file = ipo->file; io_u->flags |= IO_U_F_VER_LIST; if (ipo->flags & IP_F_TRIMMED) io_u->flags |= IO_U_F_TRIMMED; if (!fio_file_open(io_u->file)) { int r = td_io_open_file(td, io_u->file); if (r) { dprint(FD_VERIFY, "failed file %s open\n", io_u->file->file_name); return 1; } } get_file(ipo->file); assert(fio_file_open(io_u->file)); io_u->ddir = DDIR_READ; io_u->xfer_buf = io_u->buf; io_u->xfer_buflen = io_u->buflen; remove_trim_entry(td, ipo); free(ipo); dprint(FD_VERIFY, "get_next_verify: ret io_u %p\n", io_u); return 0; } dprint(FD_VERIFY, "get_next_verify: empty\n"); return 1; } void fio_verify_init(struct thread_data *td) { if (td->o.verify == VERIFY_CRC32C_INTEL || td->o.verify == VERIFY_CRC32C) { crc32c_intel_probe(); } } static void *verify_async_thread(void *data) { struct thread_data *td = data; struct io_u *io_u; int ret = 0; if (td->o.verify_cpumask_set && fio_setaffinity(td->pid, td->o.verify_cpumask)) { log_err("fio: failed setting verify thread affinity\n"); goto done; } do { FLIST_HEAD(list); read_barrier(); if (td->verify_thread_exit) break; pthread_mutex_lock(&td->io_u_lock); while (flist_empty(&td->verify_list) && !td->verify_thread_exit) { ret = pthread_cond_wait(&td->verify_cond, &td->io_u_lock); if (ret) { pthread_mutex_unlock(&td->io_u_lock); break; } } flist_splice_init(&td->verify_list, &list); pthread_mutex_unlock(&td->io_u_lock); if (flist_empty(&list)) continue; while (!flist_empty(&list)) { io_u = flist_entry(list.next, struct io_u, verify_list); flist_del(&io_u->verify_list); ret = verify_io_u(td, io_u); put_io_u(td, io_u); if (!ret) continue; if (td_non_fatal_error(td, ERROR_TYPE_VERIFY_BIT, ret)) { update_error_count(td, ret); td_clear_error(td); ret = 0; } } } while (!ret); if (ret) { td_verror(td, ret, "async_verify"); if (td->o.verify_fatal) td->terminate = 1; } done: pthread_mutex_lock(&td->io_u_lock); td->nr_verify_threads--; pthread_mutex_unlock(&td->io_u_lock); pthread_cond_signal(&td->free_cond); return NULL; } int verify_async_init(struct thread_data *td) { int i, ret; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); td->verify_thread_exit = 0; td->verify_threads = malloc(sizeof(pthread_t) * td->o.verify_async); for (i = 0; i < td->o.verify_async; i++) { ret = pthread_create(&td->verify_threads[i], &attr, verify_async_thread, td); if (ret) { log_err("fio: async verify creation failed: %s\n", strerror(ret)); break; } ret = pthread_detach(td->verify_threads[i]); if (ret) { log_err("fio: async verify thread detach failed: %s\n", strerror(ret)); break; } td->nr_verify_threads++; } pthread_attr_destroy(&attr); if (i != td->o.verify_async) { log_err("fio: only %d verify threads started, exiting\n", i); td->verify_thread_exit = 1; write_barrier(); pthread_cond_broadcast(&td->verify_cond); return 1; } return 0; } void verify_async_exit(struct thread_data *td) { td->verify_thread_exit = 1; write_barrier(); pthread_cond_broadcast(&td->verify_cond); pthread_mutex_lock(&td->io_u_lock); while (td->nr_verify_threads) pthread_cond_wait(&td->free_cond, &td->io_u_lock); pthread_mutex_unlock(&td->io_u_lock); free(td->verify_threads); td->verify_threads = NULL; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������fio-2.1.3/verify.h����������������������������������������������������������������������������������0000664�0000000�0000000�00000004146�12220322320�0013740�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef FIO_VERIFY_H #define FIO_VERIFY_H #include <stdint.h> #define FIO_HDR_MAGIC 0xacca enum { VERIFY_NONE = 0, /* no verification */ VERIFY_MD5, /* md5 sum data blocks */ VERIFY_CRC64, /* crc64 sum data blocks */ VERIFY_CRC32, /* crc32 sum data blocks */ VERIFY_CRC32C, /* crc32c sum data blocks */ VERIFY_CRC32C_INTEL, /* crc32c sum data blocks with hw */ VERIFY_CRC16, /* crc16 sum data blocks */ VERIFY_CRC7, /* crc7 sum data blocks */ VERIFY_SHA256, /* sha256 sum data blocks */ VERIFY_SHA512, /* sha512 sum data blocks */ VERIFY_META, /* block_num, timestamp etc. */ VERIFY_SHA1, /* sha1 sum data blocks */ VERIFY_PATTERN, /* verify specific patterns */ VERIFY_NULL, /* pretend to verify */ }; /* * A header structure associated with each checksummed data block. It is * followed by a checksum specific header that contains the verification * data. */ struct verify_header { uint16_t magic; uint16_t verify_type; uint32_t len; uint64_t rand_seed; uint32_t crc32; }; struct vhdr_md5 { uint32_t md5_digest[4]; }; struct vhdr_sha512 { uint8_t sha512[128]; }; struct vhdr_sha256 { uint8_t sha256[64]; }; struct vhdr_sha1 { uint32_t sha1[5]; }; struct vhdr_crc64 { uint64_t crc64; }; struct vhdr_crc32 { uint32_t crc32; }; struct vhdr_crc16 { uint16_t crc16; }; struct vhdr_crc7 { uint8_t crc7; }; struct vhdr_meta { uint64_t offset; unsigned char thread; unsigned short numberio; unsigned long time_sec; unsigned long time_usec; }; /* * Verify helpers */ extern void populate_verify_io_u(struct thread_data *, struct io_u *); extern int __must_check get_next_verify(struct thread_data *td, struct io_u *); extern int __must_check verify_io_u(struct thread_data *, struct io_u *); extern int verify_io_u_async(struct thread_data *, struct io_u *); extern void fill_pattern(struct thread_data *td, void *p, unsigned int len, struct io_u *io_u, unsigned long seed, int use_seed); extern void fio_verify_init(struct thread_data *td); /* * Async verify offload */ extern int verify_async_init(struct thread_data *); extern void verify_async_exit(struct thread_data *); #endif ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������