interbench - initial import
authorAlex Hornung <ahornung@gmail.com>
Thu, 22 Apr 2010 05:51:56 +0000 (05:51 +0000)
committerAlex Hornung <ahornung@gmail.com>
Thu, 22 Apr 2010 07:26:51 +0000 (07:26 +0000)
test/interbench/COPYING [new file with mode: 0644]
test/interbench/Makefile [new file with mode: 0644]
test/interbench/hackbench.c [new file with mode: 0644]
test/interbench/interbench.8 [new file with mode: 0644]
test/interbench/interbench.c [new file with mode: 0644]
test/interbench/interbench.h [new file with mode: 0644]
test/interbench/readme [new file with mode: 0644]
test/interbench/readme.interactivity [new file with mode: 0644]

diff --git a/test/interbench/COPYING b/test/interbench/COPYING
new file mode 100644 (file)
index 0000000..5b6e7c6
--- /dev/null
@@ -0,0 +1,340 @@
+                   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.
+\f
+                   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.)
+\f
+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.
+\f
+  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.
+\f
+  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
+\f
+           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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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.
diff --git a/test/interbench/Makefile b/test/interbench/Makefile
new file mode 100644 (file)
index 0000000..c7ace9a
--- /dev/null
@@ -0,0 +1,10 @@
+CC=gcc
+CFLAGS=-W -Wall -g -O2 -s -pipe
+LDFLAGS=-lrt -lm
+
+interbench: interbench.o hackbench.o
+interbench.o: interbench.c
+hackbench.o: hackbench.c
+
+clean:
+       rm -f *.o interbench interbench.read interbench.write interbench.loops_per_ms *~
diff --git a/test/interbench/hackbench.c b/test/interbench/hackbench.c
new file mode 100644 (file)
index 0000000..94e6571
--- /dev/null
@@ -0,0 +1,179 @@
+/* Test groups of 20 processes spraying to 20 receivers */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <semaphore.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include "interbench.h"
+
+#define DATASIZE 100
+#define LOOPS  100
+#define NUM_FDS        20
+
+static inline void barf(const char *msg)
+{
+       terminal_error(msg);
+}
+
+static void fdpair(int fds[2])
+{
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1)
+               barf("Creating fdpair");
+}
+
+/* Block until we're ready to go */
+static void ready(int ready_out, int wakefd)
+{
+       char dummy;
+       struct pollfd pollfd = { .fd = wakefd, .events = POLLIN };
+
+       /* Tell them we're ready. */
+       if (write(ready_out, &dummy, 1) != 1)
+               barf("CLIENT: ready write");
+
+       /* Wait for "GO" signal */
+       if (poll(&pollfd, 1, -1) != 1)
+               barf("poll");
+}
+
+/* Sender sprays LOOPS messages down each file descriptor */
+static void sender(int out_fd[NUM_FDS],
+                  int ready_out,
+                  int wakefd)
+{
+       char data[DATASIZE];
+       unsigned int i, j;
+
+       ready(ready_out, wakefd);
+
+       /* Now pump to every receiver. */
+       for (i = 0; i < LOOPS; i++) {
+               for (j = 0; j < NUM_FDS; j++) {
+                       int ret;
+                       unsigned long done = 0;
+
+               again:
+                       ret = write(out_fd[j], data + done, sizeof(data)-done);
+                       if (ret < 0)
+                               barf("SENDER: write");
+                       done += ret;
+                       if (done < sizeof(data))
+                               goto again;
+               }
+       }
+}
+
+/* One receiver per fd */
+static void receiver(unsigned int num_packets,
+                    int in_fd,
+                    int ready_out,
+                    int wakefd)
+{
+       unsigned int i;
+
+       /* Wait for start... */
+       ready(ready_out, wakefd);
+
+       /* Receive them all */
+       for (i = 0; i < num_packets; i++) {
+               char data[DATASIZE];
+               int ret, done = 0;
+
+       again:
+               ret = Read(in_fd, data + done, DATASIZE - done);
+               done += ret;
+               if (done < DATASIZE)
+                       goto again;
+       }
+}
+
+/* One group of senders and receivers */
+static unsigned int group(int ready_out,
+                         int wakefd)
+{
+       unsigned int i;
+       int out_fds[NUM_FDS];
+
+       for (i = 0; i < NUM_FDS; i++) {
+               int fds[2];
+
+               /* Create the pipe between client and server */
+               fdpair(fds);
+
+               /* Fork the receiver. */
+               switch (fork()) {
+               case -1: barf("fork()");
+               case 0:
+                       close(fds[1]);
+                       receiver(NUM_FDS*LOOPS, fds[0], ready_out, wakefd);
+                       exit(0);
+               }
+
+               out_fds[i] = fds[1];
+               close(fds[0]);
+       }
+
+       /* Now we have all the fds, fork the senders */
+       for (i = 0; i < NUM_FDS; i++) {
+               switch (fork()) {
+               case -1: barf("fork()");
+               case 0:
+                       sender(out_fds, ready_out, wakefd);
+                       exit(0);
+               }
+       }
+
+       /* Close the fds we have left */
+       for (i = 0; i < NUM_FDS; i++)
+               close(out_fds[i]);
+
+       /* Return number of children to reap */
+       return NUM_FDS * 2;
+}
+
+void *hackbench_thread(void *t)
+{
+       unsigned int i, num_groups, total_children;
+       int readyfds[2], wakefds[2];
+       char dummy;
+
+       num_groups = 50;
+       t = 0;
+
+       fdpair(readyfds);
+       fdpair(wakefds);
+       
+       while (1) {
+               total_children = 0;
+               for (i = 0; i < num_groups; i++)
+                       total_children += group(readyfds[1], wakefds[0]);
+       
+               /* Wait for everyone to be ready */
+               for (i = 0; i < total_children; i++)
+                       if (Read(readyfds[0], &dummy, 1) != 1)
+                               barf("Reading for readyfds");
+       
+               /* Kick them off */
+               if (write(wakefds[1], &dummy, 1) != 1)
+                       barf("Writing to start them");
+       
+               /* Reap them all */
+               for (i = 0; i < total_children; i++) {
+                       int status;
+                       wait(&status);
+                       if (!WIFEXITED(status))
+                               exit(1);
+               }
+               if (!trywait_sem(&hackthread.sem.stop))
+                       break;
+       }
+
+       post_sem(&hackthread.sem.complete);
+       return NULL;
+}
diff --git a/test/interbench/interbench.8 b/test/interbench/interbench.8
new file mode 100644 (file)
index 0000000..c70c4e3
--- /dev/null
@@ -0,0 +1,225 @@
+.TH interbench "8" "March 2006" "Interbench 0.30" "System Commands"
+.SH NAME
+interbench \-benchmark application designed to benchmark interactivity in Linux
+.SH SYNOPSIS
+.B interbench \fR\ [-l <int>] [-L <int>] [-t <int] [-B <int>] [-N <int>]
+        [-b] [-c] [-r] [-C <int> -I <int>] [-m <comment>]
+        [-w <load type>] [-x <load type>] [-W <bench>] [-X <bench>]
+        [-h]
+.SH OPTIONS
+\fB\-l\fR     Use loops per sec (default: use saved benchmark)
+.br
+\fB\-L\fR     Use cpu load of with burn load (default: 4)
+.br
+\fB\-t\fR     Seconds to run each benchmark (default: 30)
+.br
+\fB\-B\fR     Nice the benchmarked thread to (default: 0)
+.br
+\fB\-N\fR     Nice the load thread to (default: 0)
+.br
+\fB\-b\fR     Benchmark loops_per_ms even if it is already known
+.br
+\fB\-c\fR     Output to console only (default: use console and logfile)
+.br
+\fB-r\fR     Perform real time scheduling benchmarks (default: non-rt)
+.br
+\fB\-C\fR     Use percentage cpu as a custom load (default: no custom load)
+.br
+\fB\-I\fR     Use microsecond intervals for custom load (needs -C as well)
+.br
+\fB-m\fR     Add to the log file as a separate line
+.br
+\fB\-w\fR     Add to the list of loads to be tested against
+.br
+\fB\-x\fR     Exclude from the list of loads to be tested against
+.br
+\fB\-W\fR     Add <bench> to the list of benchmarks to be tested
+.br
+\fB-X\fR     Exclude <bench> from the list of benchmarks to be tested
+.br
+\fB\-h\fR     Show help
+
+If run without parameters \fBinterbench\fR will run a standard benchmark.
+.SH DESCRIPTION
+\fBinterbench\fR is designed to measure the effect of changes in Linux kernel design or system
+configuration changes such as cpu, I/O scheduler and filesystem changes and
+options. With careful benchmarking, different hardware can be compared.
+
+
+.SH What does it do?
+
+It is designed to emulate the cpu scheduling behaviour of interactive tasks and
+measure their scheduling latency and jitter. It does this with the tasks on
+their own and then in the presence of various background loads, both with
+configurable nice levels and the benchmarked tasks can be real time.
+
+.SH How does it work?
+
+First it benchmarks how best to reproduce a fixed percentage of cpu usage on the
+machine currently being used for the benchmark. It saves this to a file and then
+uses this for all subsequent runs to keep the emulation of cpu usage constant.
+
+It runs a real time high priority timing thread that wakes up the thread or
+threads of the simulated interactive tasks and then measures the latency in the
+time taken to schedule. As there is no accurate timer driven scheduling in linux
+the timing thread sleeps as accurately as linux kernel supports, and latency is
+considered as the time from this sleep till the simulated task gets scheduled.
+
+Each benchmarked simulation runs as a separate process with its own threads,
+and the background load (if any) also runs as a separate process.
+
+.SH What interactive tasks are simulated and how?
+
+.B X:
+X is simulated as a thread that uses a variable amount of cpu ranging from 0 to
+100%. This simulates an idle gui where a window is grabbed and then dragged
+across the screen.
+
+.B Audio:
+Audio is simulated as a thread that tries to run at 50ms intervals that then
+requires 5% cpu. This behaviour ignores any caching that would normally be done
+by well designed audio applications, but has been seen as the interval used to
+write to audio cards by a popular linux audio player. It also ignores any of the
+effects of different audio drivers and audio cards. Audio is also benchmarked
+running SCHED_FIFO if the real time benchmarking option is used.
+
+.B Video:
+Video is simulated as a thread that tries to receive cpu 60 times per second
+and uses 40% cpu. This would be quite a demanding video playback at 60fps. Like
+the audio simulator it ignores caching, drivers and video cards. As per audio,
+video is benchmarked with the real time option.
+
+.B Gaming:
+The cpu usage behind gaming is not at all interactive, yet games clearly are
+intended for interactive usage. This load simply uses as much cpu as it can
+get. It does not return deadlines met as there are no deadlines with an
+unlocked frame rate in a game. This does not accurately emulate a 3d game
+which is gpu bound (limited purely by the graphics card), only a cpu bound
+one.
+
+.B Custom:
+This load will allow you to specify your own combination of cpu percentage and
+intervals if you have a specific workload you are interested in and know the
+cpu usage and frame rate of it on the hardware you are testing.
+
+
+.SH What loads are simulated?
+
+.B None:
+Otherwise idle system.
+
+.B Video:
+The video simulation thread is also used as a background load.
+
+.B X:
+The X simulation thread is used as a load.
+
+.B Burn:
+A configurable number of threads fully cpu bound (4 by default).
+
+.B Write:
+A streaming write to disk repeatedly of a file the size of physical ram.
+
+.B Read:
+Repeatedly reading a file from disk the size of physical ram (to avoid any
+caching effects).
+
+.B Compile:
+Simulating a heavy 'make -j4' compilation by running Burn, Write and Read
+concurrently.
+
+.B Memload:
+Simulating heavy memory and swap pressure by repeatedly accessing 110% of
+available ram and moving it around and freeing it. You need to have some
+swap enabled due to the nature of this load, and if it detects no swap this
+load is disabled.
+
+.B Hack:
+This repeatedly runs the benchmarking program "hackbench" as 'hackbench 50'.
+This is suggested as a real time load only but because of how extreme this
+load is it is not unusual for an out-of-memory kill to occur which will
+invalidate any data you get. For this reason it is disabled by default.
+
+.B Custom:
+The custom simulation is used as a load.
+
+
+.SH What is measured and what does it mean?
+
+1. The average scheduling latency (time to requesting cpu till actually getting it) of deadlines met during the test period.
+.br
+2. The scheduling jitter is represented by calculating the standard deviation of the latency
+.br
+3. The maximum latency seen during the test period
+.br
+4. Percentage of desired cpu
+.br
+5. Percentage of deadlines met.
+
+This data is output to console and saved to a file which is stamped with the
+kernel name and date. See sample.log.
+
+.SH Sample:
+--- Benchmarking simulated cpu of X in the presence of simulated ---
+.br
+Load    Latency +/- SD (ms)  Max Latency   % Desired CPU  % Deadlines Met
+.br
+None      0.495 +/- 0.495         45             100             96
+.br
+Video      11.7 +/- 11.7        1815            89.6           62.7
+.br
+Burn       27.9 +/- 28.1        3335            78.5             44
+.br
+Write      4.02 +/- 4.03         372              97           78.7
+.br
+Read       1.09 +/- 1.09         158            99.7             88
+.br
+Compile    28.8 +/- 28.8        3351            78.2           43.7
+.br
+Memload    2.81 +/- 2.81         187            98.7             85
+
+What can be seen here is that never during this test run were all the so called
+deadlines met by the X simulator, although all the desired cpu was achieved
+under no load. In X terms this means that every bit of window movement was
+drawn while moving the window, but some were delayed and there was enough time
+to catch up before the next deadline. In the 'Burn' column we can see that only
+44% of the deadlines were met, and only 78.5% of the desired cpu was achieved.
+This means that some deadlines were so late (%deadlines met was low) that some
+redraws were dropped entirely to catch up. In X terms this would translate into
+jerky movement, in audio it would be a skip, and in video it would be a dropped
+frame. Note that despite the massive maximum latency of >3seconds, the average
+latency is still less than 30ms. This is because redraws are dropped in order
+to catch up usually by these sorts of applications.
+
+
+.SH What is relevant in the data?
+
+The results pessimise quite a lot what happens in real world terms because they
+ignore the reality of buffering, but this allows us to pick up subtle 
+differences more readily. In terms of what would be noticed by the end user,
+dropping deadlines would make noticable clicks in audio, subtle visible frame
+time delays in video, and loss of "smooth" movement in X. Dropping desired cpu
+would be much more noticeable with audio skips, missed video frames or jerks
+in window movement under X. The magnitude of these would be best represented by
+the maximum latency. When the deadlines are actually met, the average latency
+represents how "smooth" it would look. Average humans' limit of perception for
+jitter is in the order of 7ms. Trained audio observers might notice much less.
+
+.SH AUTHOR
+Written by Con Kolivas.
+
+This manual page was written for the Debian system by
+Julien Valroff <julien@kirya.net>.
+.SH "REPORTING BUGS"
+Report bugs to <kernel@kolivas.org>.
+.SH COPYRIGHT
+Copyright 2006 Con Kolivas <kernel@kolivas.org>
+
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+http://interbench.kolivas.org
+.br
+/usr/share/doc/interbench/readme.gz
+.br
+/usr/share/doc/interbench/readme.interactivity
diff --git a/test/interbench/interbench.c b/test/interbench/interbench.c
new file mode 100644 (file)
index 0000000..7cebbd1
--- /dev/null
@@ -0,0 +1,1752 @@
+/*******************************************
+ *
+ * Interbench - Interactivity benchmark
+ *
+ * Author:  Con Kolivas <kernel@kolivas.org>
+ *
+ * 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.
+ *
+ *******************************************/
+
+#define _GNU_SOURCE
+#define _FILE_OFFSET_BITS 64   /* Large file support */
+#define INTERBENCH_VERSION     "0.30"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <strings.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <time.h>
+#include <errno.h>
+#include <semaphore.h>
+#include <pthread.h>
+#include <math.h>
+#include <fenv.h>
+#include <signal.h>
+#include <sys/utsname.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include "interbench.h"
+
+#define MAX_UNAME_LENGTH       100
+#define MAX_LOG_LENGTH         ((MAX_UNAME_LENGTH) + 4)
+#define MIN_BLK_SIZE           1024
+#define DEFAULT_RESERVE                64
+#define MB                     (1024 * 1024)   /* 2^20 bytes */
+#define KB                     1024
+#define MAX_MEM_IN_MB          (1024 * 64)     /* 64 GB */
+
+struct user_data {
+       unsigned long loops_per_ms;
+       unsigned long ram, swap;
+       int duration;
+       int do_rt;
+       int bench_nice;
+       int load_nice;
+       unsigned long custom_run;
+       unsigned long custom_interval;
+       unsigned long cpu_load;
+       char logfilename[MAX_LOG_LENGTH];
+       int log;
+       char unamer[MAX_UNAME_LENGTH];
+       char datestamp[13];
+       FILE *logfile;
+} ud = {
+       .duration = 30,
+       .cpu_load = 4,
+       .log = 1,
+};
+
+/* Pipes main to/from load and bench processes */
+static int m2l[2], l2m[2], m2b[2], b2m[2];
+
+/* Which member of becnhmarks is used when not benchmarking */
+#define NOT_BENCHING   (THREADS)
+#define CUSTOM         (THREADS - 1)
+
+/*
+ * To add another load or a benchmark you need to increment the value of
+ * THREADS, add a function prototype for your function and add an entry to
+ * the threadlist. To specify whether the function is a benchmark or a load
+ * set the benchmark and/or load flag as appropriate. The basic requirements
+ * of a new load can be seen by using emulate_none as a template.
+ */
+
+void emulate_none(struct thread *th);
+void emulate_audio(struct thread *th);
+void emulate_video(struct thread *th);
+void emulate_x(struct thread *th);
+void emulate_game(struct thread *th);
+void emulate_burn(struct thread *th);
+void emulate_write(struct thread *th);
+void emulate_read(struct thread *th);
+void emulate_ring(struct thread *th);
+void emulate_compile(struct thread *th);
+void emulate_memload(struct thread *th);
+void emulate_hackbench(struct thread *th);
+void emulate_custom(struct thread *th);
+
+struct thread threadlist[THREADS] = {
+       {.label = "None", .name = emulate_none, .load = 1, .rtload = 1},
+       {.label = "Audio", .name = emulate_audio, .bench = 1, .rtbench = 1},
+       {.label = "Video", .name = emulate_video, .bench = 1, .rtbench = 1, .load = 1, .rtload = 1},
+       {.label = "X", .name = emulate_x, .bench = 1, .load = 1, .rtload = 1},
+       {.label = "Gaming", .name = emulate_game, .nodeadlines = 1, .bench = 1},
+       {.label = "Burn", .name = emulate_burn, .load = 1, .rtload = 1},
+       {.label = "Write", .name = emulate_write, .load = 1, .rtload = 1},
+       {.label = "Read", .name = emulate_read, .load = 1, .rtload = 1},
+       {.label = "Ring", .name = emulate_ring, .load = 0, .rtload = 0},        /* No useful data from this */
+       {.label = "Compile", .name = emulate_compile, .load = 1, .rtload = 1},
+       {.label = "Memload", .name = emulate_memload, .load = 1, .rtload = 1},
+       {.label = "Hack", .name = emulate_hackbench, .load = 0, .rtload = 0},   /* This is causing signal headaches */
+       {.label = "Custom", .name = emulate_custom},    /* Leave custom as last entry */
+};
+
+void init_sem(sem_t *sem);
+void init_all_sems(struct sems *s);
+void initialise_thread(int i);
+void start_thread(struct thread *th);
+void stop_thread(struct thread *th);
+
+void terminal_error(const char *name)
+{
+       fprintf(stderr, "\n");
+       perror(name);
+       exit (1);
+}
+
+void terminal_fileopen_error(FILE *fp, char *name)
+{
+       if (fclose(fp) == -1)
+               terminal_error("fclose");
+       terminal_error(name);
+}
+
+unsigned long long get_nsecs(struct timespec *myts)
+{
+       if (clock_gettime(CLOCK_REALTIME, myts))
+               terminal_error("clock_gettime");
+       return (myts->tv_sec * 1000000000 + myts->tv_nsec );
+}
+
+unsigned long get_usecs(struct timespec *myts)
+{
+       if (clock_gettime(CLOCK_REALTIME, myts))
+               terminal_error("clock_gettime");
+       return (myts->tv_sec * 1000000 + myts->tv_nsec / 1000 );
+}
+
+void set_fifo(int prio)
+{
+       struct sched_param sp;
+
+       memset(&sp, 0, sizeof(sp));
+       sp.sched_priority = prio;
+       if (sched_setscheduler(0, SCHED_FIFO, &sp) == -1) {
+               if (errno != EPERM)
+                       terminal_error("sched_setscheduler");
+       }
+}
+
+void set_mlock(void)
+{
+       int mlockflags;
+
+       mlockflags = MCL_CURRENT | MCL_FUTURE;
+       mlockall(mlockflags);   /* Is not critical if this fails */
+}
+
+void set_munlock(void)
+{
+       if (munlockall() == -1)
+               terminal_error("munlockall");
+}
+
+void set_thread_fifo(pthread_t pthread, int prio)
+{
+       struct sched_param sp;
+       memset(&sp, 0, sizeof(sp));
+       sp.sched_priority = prio;
+       if (pthread_setschedparam(pthread, SCHED_FIFO, &sp) == -1)
+               terminal_error("pthread_setschedparam");
+}
+
+void set_normal(void)
+{
+       struct sched_param sp;
+       memset(&sp, 0, sizeof(sp));
+       sp.sched_priority = 0;
+       if (sched_setscheduler(0, SCHED_OTHER, &sp) == -1) {
+               fprintf(stderr, "Weird, could not unset RT scheduling!\n");
+       }
+}
+
+void set_nice(int prio)
+{
+       if (setpriority(PRIO_PROCESS, 0, prio) == -1)
+               terminal_error("setpriority");
+}
+
+int test_fifo(void)
+{
+       struct sched_param sp;
+       memset(&sp, 0, sizeof(sp));
+       sp.sched_priority = 99;
+       if (sched_setscheduler(0, SCHED_FIFO, &sp) == -1) {
+               if (errno != EPERM)
+                       terminal_error("sched_setscheduler");
+               goto out_fail;
+       }
+       if (sched_getscheduler(0) != SCHED_FIFO)
+               goto out_fail;
+       set_normal();
+       return 1;
+out_fail:
+       set_normal();
+       return 0;
+}
+
+void set_thread_normal(pthread_t pthread)
+{
+       struct sched_param sp;
+       memset(&sp, 0, sizeof(sp));
+       sp.sched_priority = 0;
+       if (pthread_setschedparam(pthread, SCHED_OTHER, &sp) == -1)
+               terminal_error("pthread_setschedparam");
+}
+
+void sync_flush(void)
+{
+       if ((fflush(NULL)) == EOF)
+               terminal_error("fflush");
+       sync();
+       sync();
+       sync();
+}
+
+unsigned long compute_allocable_mem(void)
+{
+       unsigned long total = ud.ram + ud.swap;
+       unsigned long usage = ud.ram * 110 / 100 ;
+
+       /* Leave at least DEFAULT_RESERVE free space and check for maths overflow. */
+       if (total - DEFAULT_RESERVE < usage)
+               usage = total - DEFAULT_RESERVE;
+       usage /= 1024;  /* to megabytes */
+       if (usage > 2930)
+               usage = 2930;
+       return usage;
+}
+
+void burn_loops(unsigned long loops)
+{
+       unsigned long i;
+
+       /*
+        * We need some magic here to prevent the compiler from optimising
+        * this loop away. Otherwise trying to emulate a fixed cpu load
+        * with this loop will not work.
+        */
+       for (i = 0 ; i < loops ; i++)
+            asm volatile("" : : : "memory");
+}
+
+/* Use this many usecs of cpu time */
+void burn_usecs(unsigned long usecs)
+{
+       unsigned long ms_loops;
+
+       ms_loops = ud.loops_per_ms / 1000 * usecs;
+       burn_loops(ms_loops);
+}
+
+void microsleep(unsigned long long usecs)
+{
+       struct timespec req, rem;
+
+       rem.tv_sec = rem.tv_nsec = 0;
+
+       req.tv_sec = usecs / 1000000;
+       req.tv_nsec = (usecs - (req.tv_sec * 1000000)) * 1000;
+continue_sleep:
+       if ((nanosleep(&req, &rem)) == -1) {
+               if (errno == EINTR) {
+                       if (rem.tv_sec || rem.tv_nsec) {
+                               req.tv_sec = rem.tv_sec;
+                               req.tv_nsec = rem.tv_nsec;
+                               goto continue_sleep;
+                       }
+                       goto out;
+               }
+               terminal_error("nanosleep");
+       }
+out:
+       return;
+}
+
+/*
+ * Yes, sem_post and sem_wait shouldn't return -1 but they do so we must
+ * handle it.
+ */
+inline void post_sem(sem_t *s)
+{
+retry:
+       if ((sem_post(s)) == -1) {
+               if (errno == EINTR)
+                       goto retry;
+               terminal_error("sem_post");
+       }
+}
+
+inline void wait_sem(sem_t *s)
+{
+retry:
+       if ((sem_wait(s)) == -1) {
+               if (errno == EINTR)
+                       goto retry;
+               terminal_error("sem_wait");
+       }
+}
+
+inline int trywait_sem(sem_t *s)
+{
+       int ret;
+
+retry:
+       if ((ret = sem_trywait(s)) == -1) {
+               if (errno == EINTR)
+                       goto retry;
+               if (errno != EAGAIN)
+                       terminal_error("sem_trywait");
+       }
+       return ret;
+}
+
+inline ssize_t Read(int fd, void *buf, size_t count)
+{
+       ssize_t retval;
+
+retry:
+       retval = read(fd, buf, count);
+       if (retval == -1) {
+               if (errno == EINTR)
+                       goto retry;
+               terminal_error("read");
+       }
+       return retval;
+}
+
+inline ssize_t Write(int fd, const void *buf, size_t count)
+{
+       ssize_t retval;
+
+retry:
+       retval = write(fd, &buf, count);
+       if (retval == -1) {
+               if (errno == EINTR)
+                       goto retry;
+               terminal_error("write");
+       }
+       return retval;
+}
+
+unsigned long periodic_schedule(struct thread *th, unsigned long run_usecs,
+       unsigned long interval_usecs, unsigned long long deadline)
+{
+       unsigned long long latency, missed_latency;
+       unsigned long long current_time;
+       struct tk_thread *tk;
+       struct data_table *tb;
+       struct timespec myts;
+
+       latency = 0;
+       tb = th->dt;
+       tk = &th->tkthread;
+
+       current_time = get_usecs(&myts);
+       if (current_time > deadline + tk->slept_interval)
+               latency = current_time - deadline- tk->slept_interval;
+
+       /* calculate the latency for missed frames */
+       missed_latency = 0;
+
+       current_time = get_usecs(&myts);
+       if (interval_usecs && current_time > deadline + interval_usecs) {
+               /* We missed the deadline even before we consumed cpu */
+               unsigned long intervals;
+
+               deadline += interval_usecs;
+               intervals = (current_time - deadline) /
+                       interval_usecs + 1;
+
+               tb->missed_deadlines += intervals;
+               missed_latency = intervals * interval_usecs;
+               deadline += intervals * interval_usecs;
+               tb->missed_burns += intervals;
+               goto bypass_burn;
+       }
+
+       burn_usecs(run_usecs);
+       current_time = get_usecs(&myts);
+       tb->achieved_burns++;
+
+       /*
+        * If we meet the deadline we move the deadline forward, otherwise
+        * we consider it a missed deadline and dropped frame etc.
+        */
+       deadline += interval_usecs;
+       if (deadline >= current_time) {
+               tb->deadlines_met++;
+       } else {
+               if (interval_usecs) {
+                       unsigned long intervals = (current_time - deadline) /
+                               interval_usecs + 1;
+       
+                       tb->missed_deadlines += intervals;
+                       missed_latency = intervals * interval_usecs;
+                       deadline += intervals * interval_usecs;
+                       if (intervals > 1)
+                               tb->missed_burns += intervals;
+               } else {
+                       deadline = current_time;
+                       goto out_nosleep;
+               }
+       }
+bypass_burn:
+       tk->sleep_interval = deadline - current_time;
+
+       post_sem(&tk->sem.start);
+       wait_sem(&tk->sem.complete);
+out_nosleep:
+       /* 
+        * Must add missed_latency to total here as this function may not be
+        * called again and the missed latency can be lost
+        */
+       latency += missed_latency;
+       if (latency > tb->max_latency)
+               tb->max_latency = latency;
+       tb->total_latency += latency;
+       tb->sum_latency_squared += latency * latency;
+       tb->nr_samples++;
+
+       return deadline;
+}
+
+void initialise_thread_data(struct data_table *tb)
+{
+       tb->max_latency =
+               tb->total_latency =
+               tb->sum_latency_squared =
+               tb->deadlines_met =
+               tb->missed_deadlines =
+               tb->missed_burns =
+               tb->nr_samples = 0;
+}
+
+void create_pthread(pthread_t  * thread, pthread_attr_t * attr,
+       void * (*start_routine)(void *), void *arg)
+{
+       if (pthread_create(thread, attr, start_routine, arg))
+               terminal_error("pthread_create");
+}
+
+void join_pthread(pthread_t th, void **thread_return)
+{
+       if (pthread_join(th, thread_return))
+               terminal_error("pthread_join");
+}
+
+void emulate_none(struct thread *th)
+{
+       sem_t *s = &th->sem.stop;
+       wait_sem(s);
+}
+
+#define AUDIO_INTERVAL (50000)
+#define AUDIO_RUN      (AUDIO_INTERVAL / 20)
+/* We emulate audio by using 5% cpu and waking every 50ms */
+void emulate_audio(struct thread *th)
+{
+       unsigned long long deadline;
+       sem_t *s = &th->sem.stop;
+       struct timespec myts;
+
+       th->decasecond_deadlines = 1000000 / AUDIO_INTERVAL * 10;
+       deadline = get_usecs(&myts);
+
+       while (1) {
+               deadline = periodic_schedule(th, AUDIO_RUN, AUDIO_INTERVAL,
+                       deadline);
+               if (!trywait_sem(s))
+                       return;
+       }
+}
+
+/* We emulate video by using 40% cpu and waking for 60fps */
+#define VIDEO_INTERVAL (1000000 / 60)
+#define VIDEO_RUN      (VIDEO_INTERVAL * 40 / 100)
+void emulate_video(struct thread *th)
+{
+       unsigned long long deadline;
+       sem_t *s = &th->sem.stop;
+       struct timespec myts;
+
+       th->decasecond_deadlines = 1000000 / VIDEO_INTERVAL * 10;
+       deadline = get_usecs(&myts);
+
+       while (1) {
+               deadline = periodic_schedule(th, VIDEO_RUN, VIDEO_INTERVAL,
+                       deadline);
+               if (!trywait_sem(s))
+                       return;
+       }
+}
+
+/*
+ * We emulate X by running for a variable percentage of cpu from 0-100% 
+ * in 1ms chunks.
+ */
+void emulate_x(struct thread *th)
+{
+       unsigned long long deadline;
+       sem_t *s = &th->sem.stop;
+       struct timespec myts;
+
+       th->decasecond_deadlines = 100;
+       deadline = get_usecs(&myts);
+
+       while (1) {
+               int i, j;
+               for (i = 0 ; i <= 100 ; i++) {
+                       j = 100 - i;
+                       deadline = periodic_schedule(th, i * 1000, j * 1000,
+                               deadline);
+                       deadline += i * 1000;
+                       if (!trywait_sem(s))
+                               return;
+               }
+       }
+}
+
+/* 
+ * We emulate gaming by using 100% cpu and seeing how many frames (jobs
+ * completed) we can do in that time. Deadlines are meaningless with 
+ * unlocked frame rates. We do not use periodic schedule because for
+ * this load because this never wants to sleep.
+ */
+#define GAME_INTERVAL  (100000)
+#define GAME_RUN       (GAME_INTERVAL)
+void emulate_game(struct thread *th)
+{
+       unsigned long long deadline, current_time, latency;
+       sem_t *s = &th->sem.stop;
+       struct timespec myts;
+       struct data_table *tb;
+
+       tb = th->dt;
+       th->decasecond_deadlines = 1000000 / GAME_INTERVAL * 10;
+
+       while (1) {
+               deadline = get_usecs(&myts) + GAME_INTERVAL;
+               burn_usecs(GAME_RUN);
+               current_time = get_usecs(&myts);
+               /* use usecs instead of simple count for game burn statistics */
+               tb->achieved_burns += GAME_RUN;
+               if (current_time > deadline) {
+                       latency = current_time - deadline;
+                       tb->missed_burns += latency;
+               } else
+                       latency = 0;
+               if (latency > tb->max_latency)
+                       tb->max_latency = latency;
+               tb->total_latency += latency;
+               tb->sum_latency_squared += latency * latency;
+               tb->nr_samples++;
+               if (!trywait_sem(s))
+                       return;
+       }
+}
+
+void *burn_thread(void *t)
+{
+       struct thread *th;
+       sem_t *s;
+       long i = (long)t;
+
+       th = &threadlist[i];
+       s = &th->sem.stopchild;
+
+       while (1) {
+               burn_loops(ud.loops_per_ms);
+               if (!trywait_sem(s)) {
+                       post_sem(s);
+                       break;
+               }
+       }
+       return NULL;
+}
+
+/* Have ud.cpu_load threads burn cpu continuously */
+void emulate_burn(struct thread *th)
+{
+       sem_t *s = &th->sem.stop;
+       unsigned long i;
+       long t;
+       pthread_t burnthreads[ud.cpu_load];
+
+       t = th->threadno;
+       for (i = 0 ; i < ud.cpu_load ; i++)
+               create_pthread(&burnthreads[i], NULL, burn_thread,
+                       (void*)(long) t);
+       wait_sem(s);
+       post_sem(&th->sem.stopchild);
+       for (i = 0 ; i < ud.cpu_load ; i++)
+               join_pthread(burnthreads[i], NULL);
+}
+
+/* Write a file the size of ram continuously */
+void emulate_write(struct thread *th)
+{
+       sem_t *s = &th->sem.stop;
+       FILE *fp;
+       char *name = "interbench.write";
+       void *buf = NULL;
+       struct stat statbuf;
+       unsigned long mem;
+
+       if (!(fp = fopen(name, "w")))
+               terminal_error("fopen");
+       if (stat(name, &statbuf) == -1)
+               terminal_fileopen_error(fp, "stat");
+       if (statbuf.st_blksize < MIN_BLK_SIZE)
+               statbuf.st_blksize = MIN_BLK_SIZE;
+       mem = ud.ram / (statbuf.st_blksize / 1024);     /* kilobytes to blocks */
+       if (!(buf = calloc(1, statbuf.st_blksize)))
+               terminal_fileopen_error(fp, "calloc");
+       if (fclose(fp) == -1)
+               terminal_error("fclose");
+
+       while (1) {
+               unsigned int i;
+
+               if (!(fp = fopen(name, "w")))
+                       terminal_error("fopen");
+               if (stat(name, &statbuf) == -1)
+                       terminal_fileopen_error(fp, "stat");
+               for (i = 0 ; i < mem; i++) {
+                       if (fwrite(buf, statbuf.st_blksize, 1, fp) != 1)
+                               terminal_fileopen_error(fp, "fwrite");
+                       if (!trywait_sem(s))
+                               goto out;
+               }
+               if (fclose(fp) == -1)
+                       terminal_error("fclose");
+       }
+
+out:
+       if (fclose(fp) == -1)
+               terminal_error("fclose");
+       if (remove(name) == -1)
+               terminal_error("remove");
+       sync_flush();
+}
+
+/* Read a file the size of ram continuously */
+void emulate_read(struct thread *th)
+{
+       sem_t *s = &th->sem.stop;
+       char *name = "interbench.read";
+       void *buf = NULL;
+       struct stat statbuf;
+       unsigned long bsize;
+       int tmp;
+
+       if ((tmp = open(name, O_RDONLY)) == -1)
+               terminal_error("open");
+       if (stat(name, &statbuf) == -1) 
+               terminal_error("stat");
+       bsize = statbuf.st_blksize;
+       if (!(buf = malloc(bsize)))
+               terminal_error("malloc");
+
+       while (1) {
+               int rd;
+
+               /* 
+                * We have to read the whole file before quitting the load
+                * to prevent the data being cached for the next read. This
+                * is also the reason the file is the size of physical ram.
+                */
+               while ((rd = Read(tmp , buf, bsize)) > 0);
+               if(!trywait_sem(s))
+                       return;
+               if (lseek(tmp, (off_t)0, SEEK_SET) == -1)
+                       terminal_error("lseek");
+       }
+}
+
+#define RINGTHREADS    4
+
+struct thread ringthreads[RINGTHREADS];
+
+void *ring_thread(void *t)
+{
+       struct thread *th;
+       struct sems *s;
+       int i, post_to;
+
+       i = (long)t;
+       th = &ringthreads[i];
+       s = &th->sem;
+       post_to = i + 1;
+       if (post_to == RINGTHREADS)
+               post_to = 0;
+       if (i == 0)
+               post_sem(&s->ready);
+
+       while (1) {
+               wait_sem(&s->start);
+               post_sem(&ringthreads[post_to].sem.start);
+               if (!trywait_sem(&s->stop))
+                       goto out;
+       }
+out:   
+       post_sem(&ringthreads[post_to].sem.start);
+       post_sem(&s->complete);
+       return NULL;
+}
+
+/* Create a ring of 4 processes that wake each other up in a circle */
+void emulate_ring(struct thread *th)
+{
+       sem_t *s = &th->sem.stop;
+       int i;
+
+       for (i = 0 ; i < RINGTHREADS ; i++) {
+               init_all_sems(&ringthreads[i].sem);
+               create_pthread(&ringthreads[i].pthread, NULL, 
+                       ring_thread, (void*)(long) i);
+       }
+
+       wait_sem(&ringthreads[0].sem.ready);
+       post_sem(&ringthreads[0].sem.start);
+       wait_sem(s);
+       for (i = 0 ; i < RINGTHREADS ; i++)
+               post_sem(&ringthreads[i].sem.stop);
+       for (i = 0 ; i < RINGTHREADS ; i++) {
+               wait_sem(&ringthreads[i].sem.complete);
+               join_pthread(ringthreads[i].pthread, NULL);
+       }
+}
+
+/* We emulate a compile by running burn, write and read threads simultaneously */
+void emulate_compile(struct thread *th)
+{
+       sem_t *s = &th->sem.stop;
+       unsigned long i, threads[3];
+
+       for (i = 0 ; i < THREADS ; i++) {
+               if (threadlist[i].label == "Burn")
+                       threads[0] = i;
+               if (threadlist[i].label == "Write")
+                       threads[1] = i;
+               if (threadlist[i].label == "Read")
+                       threads[2] = i;
+       }
+       for (i = 0 ; i < 3 ; i++) {
+               if (!threads[i]) {
+                       fprintf(stderr, "Can't find all threads for compile load\n");
+                       exit(1);
+               }
+       }
+       for (i = 0 ; i < 3 ; i++) {
+               initialise_thread(threads[i]);
+               start_thread(&threadlist[threads[i]]);
+       }
+       wait_sem(s);
+       for (i = 0 ; i < 3 ; i++)
+               stop_thread(&threadlist[threads[i]]);
+}
+
+int *grab_and_touch (char *block[], int i)
+{
+       block[i] = (char *) malloc(MB);
+       if (!block[i])
+               return NULL;
+       return (memset(block[i], 1, MB));
+}
+
+/* We emulate a memory load by allocating and torturing 110% of available ram */
+void emulate_memload(struct thread *th)
+{
+       sem_t *s = &th->sem.stop;
+       unsigned long touchable_mem, i;
+       char *mem_block[MAX_MEM_IN_MB];
+       void *success;
+
+       touchable_mem = compute_allocable_mem();
+       /* loop until we're killed, frobbing memory in various perverted ways */
+       while (1) {
+               for (i = 0;  i < touchable_mem; i++) {
+                       success = grab_and_touch(mem_block, i);
+                       if (!success) {
+                               touchable_mem = i-1;
+                               break;
+                       }
+               }
+               if (!trywait_sem(s))
+                       goto out_freemem;
+               for (i = 0;  i < touchable_mem; i++) {
+                       memcpy(mem_block[i], mem_block[(i + touchable_mem / 2) %
+                               touchable_mem], MB);
+                       if (!trywait_sem(s))
+                               goto out_freemem;
+               }
+               for (i = 0; i < touchable_mem; i++) {
+                       free(mem_block[i]);
+               }       
+               if (!trywait_sem(s))
+                       goto out;
+       }
+out_freemem:
+       for (i = 0; i < touchable_mem; i++)
+               free(mem_block[i]);
+out:
+       return;
+}
+
+struct thread hackthread;
+
+void emulate_hackbench(struct thread *th)
+{
+       sem_t *s = &th->sem.stop;
+
+       init_all_sems(&hackthread.sem);
+       create_pthread(&hackthread.pthread, NULL, hackbench_thread, (void *) 0);
+
+       wait_sem(s);
+
+       post_sem(&hackthread.sem.stop);
+       wait_sem(&hackthread.sem.complete);
+
+       join_pthread(hackthread.pthread, NULL);
+}
+
+#define CUSTOM_INTERVAL        (ud.custom_interval)
+#define CUSTOM_RUN     (ud.custom_run)
+void emulate_custom(struct thread *th)
+{
+       unsigned long long deadline;
+       sem_t *s = &th->sem.stop;
+       struct timespec myts;
+
+       th->decasecond_deadlines = 1000000 / CUSTOM_INTERVAL * 10;
+       deadline = get_usecs(&myts);
+
+       while (1) {
+               deadline = periodic_schedule(th, CUSTOM_RUN, CUSTOM_INTERVAL,
+                       deadline);
+               if (!trywait_sem(s))
+                       return;
+       }
+}
+
+void *timekeeping_thread(void *t)
+{
+       struct thread *th;
+       struct tk_thread *tk;
+       struct sems *s;
+       struct timespec myts;
+       long i = (long)t;
+
+       th = &threadlist[i];
+       tk = &th->tkthread;
+       s = &th->tkthread.sem;
+       /*
+        * If this timekeeping thread is that of a benchmarked thread we run
+        * even higher priority than the benched thread is if running real
+        * time. Otherwise, the load timekeeping thread, which does not need
+        * accurate accounting remains SCHED_NORMAL;
+        */
+       if (th->dt != &th->benchmarks[NOT_BENCHING])
+               set_fifo(96);
+       /* These values must be changed at the appropriate places or race */
+       tk->sleep_interval = tk->slept_interval = 0;
+       post_sem(&s->ready);
+
+       while (1) {
+               unsigned long start_time, now;
+
+               if (!trywait_sem(&s->stop))
+                       goto out;
+               wait_sem(&s->start);
+               tk->slept_interval = 0;
+               start_time = get_usecs(&myts);
+               if (!trywait_sem(&s->stop))
+                       goto out;
+               if (tk->sleep_interval) {
+                       unsigned long diff = 0;
+                       microsleep(tk->sleep_interval);
+                       now = get_usecs(&myts);
+                       /* now should always be > start_time but... */
+                       if (now > start_time) {
+                               diff = now - start_time;
+                               if (diff > tk->sleep_interval)
+                                       tk->slept_interval = diff -
+                                               tk->sleep_interval;
+                       }
+               }
+               tk->sleep_interval = 0;
+               post_sem(&s->complete);
+       }
+out:
+       return NULL;
+}
+
+/*
+ * All the sleep functions such as nanosleep can only guarantee that they
+ * sleep for _at least_ the time requested. We work around this by having
+ * a high priority real time thread that accounts for the extra time slept
+ * in nanosleep. This allows wakeup latency of the tested thread to be
+ * accurate and reflect true scheduling delays.
+ */
+void *emulation_thread(void *t)
+{
+       struct thread *th;
+       struct tk_thread *tk;
+       struct sems *s, *tks;
+       long i = (long)t;
+
+       th = &threadlist[i];
+       tk = &th->tkthread;
+       s = &th->sem;
+       tks = &tk->sem;
+       init_all_sems(tks);
+
+       /* Start the timekeeping thread */
+       create_pthread(&th->tk_pthread, NULL, timekeeping_thread,
+               (void*)(long) i);
+       /* Wait for timekeeping thread to be ready */
+       wait_sem(&tks->ready);
+
+       /* Tell main we're ready to start*/
+       post_sem(&s->ready);
+
+       /* Wait for signal from main to start thread */
+       wait_sem(&s->start);
+
+       /* Start the actual function being benched/or running as load */
+       th->name(th);
+
+       /* Stop the timekeeping thread */
+       post_sem(&tks->stop);
+       post_sem(&tks->start);
+       join_pthread(th->tk_pthread, NULL);
+
+       /* Tell main we've finished */
+       post_sem(&s->complete);
+       return NULL;
+}
+
+/*
+ * In an unoptimised loop we try to benchmark how many meaningless loops
+ * per second we can perform on this hardware to fairly accurately
+ * reproduce certain percentage cpu usage
+ */
+void calibrate_loop(void)
+{
+       unsigned long long start_time, loops_per_msec, run_time = 0;
+       unsigned long loops;
+       struct timespec myts;
+
+       loops_per_msec = 100000;
+redo:
+       /* Calibrate to within 1% accuracy */
+       while (run_time > 1010000 || run_time < 990000) {
+               loops = loops_per_msec;
+               start_time = get_nsecs(&myts);
+               burn_loops(loops);
+               run_time = get_nsecs(&myts) - start_time;
+               loops_per_msec = (1000000 * loops_per_msec / run_time ? :
+                       loops_per_msec);
+       }
+
+       /* Rechecking after a pause increases reproducibility */
+       sleep(1);
+       loops = loops_per_msec;
+       start_time = get_nsecs(&myts);
+       burn_loops(loops);
+       run_time = get_nsecs(&myts) - start_time;
+
+       /* Tolerate 5% difference on checking */
+       if (run_time > 1050000 || run_time < 950000)
+               goto redo;
+
+       ud.loops_per_ms = loops_per_msec;
+}
+
+void log_output(const char *format, ...) __attribute__ ((format(printf, 1, 2)));
+
+/* Output to console +/- logfile */
+void log_output(const char *format, ...)
+{
+       va_list ap;
+
+       va_start(ap, format);
+       if (vprintf(format, ap) == -1)
+               terminal_error("vprintf");
+       va_end(ap);
+       if (ud.log) {
+               va_start(ap, format);
+               if (vfprintf(ud.logfile, format, ap) == -1)
+                       terminal_error("vpfrintf");
+               va_end(ap);
+       }
+       fflush(NULL);
+}
+
+/* Calculate statistics and output them */
+void show_latencies(struct thread *th)
+{
+       struct data_table *tbj;
+       struct tk_thread *tk;
+       double average_latency, deadlines_met, samples_met, sd, max_latency;
+       long double variance = 0;
+
+       tbj = th->dt;
+       tk = &th->tkthread;
+
+       if (tbj->nr_samples > 1) {
+               average_latency = tbj->total_latency / tbj->nr_samples;
+               variance = (tbj->sum_latency_squared - (average_latency *
+                       average_latency) / tbj->nr_samples) / (tbj->nr_samples - 1);
+               sd = sqrtl(variance);
+       } else {
+               average_latency = tbj->total_latency;
+               sd = 0.0;
+       }
+
+       /*
+        * Landing on the boundary of a deadline can make loaded runs appear
+        * to do more work than unloaded due to tiny duration differences.
+        */
+       if (tbj->achieved_burns > 0)
+               samples_met = (double)tbj->achieved_burns /
+                   (double)(tbj->achieved_burns + tbj->missed_burns) * 100;
+       else
+               samples_met = 0.0;
+       max_latency = tbj->max_latency;
+       /* When benchmarking rt we represent the data in us */
+       if (!ud.do_rt) {
+               average_latency /= 1000;
+               sd /= 1000;
+               max_latency /= 1000;
+       }
+       if (tbj->deadlines_met == 0)
+               deadlines_met = 0;
+       else
+               deadlines_met = (double)tbj->deadlines_met /
+                   (double)(tbj->missed_deadlines + tbj->deadlines_met) * 100;
+
+       /* Messy nonsense to format the output nicely */
+       if (average_latency >= 100)
+               log_output("%7.0f +/- ", average_latency);
+       else
+               log_output("%7.3g +/- ", average_latency);
+       if (sd >= 100)
+               log_output("%-9.0f", sd);
+       else
+               log_output("%-9.3g", sd);
+       if (max_latency >= 100)
+               log_output("%7.0f\t", max_latency);
+       else
+               log_output("%7.3g\t", max_latency);
+       log_output("\t%4.3g", samples_met);
+       if (!th->nodeadlines)
+               log_output("\t%11.3g", deadlines_met);
+       log_output("\n");
+       sync_flush();
+}
+
+void create_read_file(void)
+{
+       unsigned int i;
+       FILE *fp;
+       char *name = "interbench.read";
+       void *buf = NULL;
+       struct stat statbuf;
+       unsigned long mem, bsize;
+       int tmp;
+
+       if ((tmp = open(name, O_RDONLY)) == -1) {
+               if (errno != ENOENT)
+                       terminal_error("open");
+               goto write;
+       }
+       if (stat(name, &statbuf) == -1)
+               terminal_error("stat");
+       if (statbuf.st_blksize < MIN_BLK_SIZE)
+               statbuf.st_blksize = MIN_BLK_SIZE;
+       bsize = statbuf.st_blksize;
+       if (statbuf.st_size / 1024 / bsize == ud.ram / bsize)
+               return;
+       if (remove(name) == -1)
+               terminal_error("remove");
+write:
+       fprintf(stderr,"Creating file for read load...\n");
+       if (!(fp = fopen(name, "w")))
+               terminal_error("fopen");
+       if (stat(name, &statbuf) == -1)
+               terminal_fileopen_error(fp, "stat");
+       if (statbuf.st_blksize < MIN_BLK_SIZE)
+               statbuf.st_blksize = MIN_BLK_SIZE;
+       bsize = statbuf.st_blksize;
+       if (!(buf = calloc(1, bsize)))
+               terminal_fileopen_error(fp, "calloc");
+       mem = ud.ram / (bsize / 1024);  /* kilobytes to blocks */
+
+       for (i = 0 ; i < mem; i++) {
+               if (fwrite(buf, bsize, 1, fp) != 1)
+                       terminal_fileopen_error(fp, "fwrite");
+       }
+       if (fclose(fp) == -1)
+               terminal_error("fclose");
+       sync_flush();
+}
+
+void get_ram(void)
+{
+       FILE *meminfo;
+        char aux[256];
+       if(!(meminfo = fopen("/proc/meminfo", "r")))
+               terminal_error("fopen");
+
+       ud.ram = ud.swap = 0;
+       while( !feof(meminfo) && !fscanf(meminfo, "MemTotal: %lu kB", &ud.ram) )
+            fgets(aux,sizeof(aux),meminfo);
+       while( !feof(meminfo) && !fscanf(meminfo, "SwapTotal: %lu kB", &ud.swap) )
+            fgets(aux,sizeof(aux),meminfo);
+       if (fclose(meminfo) == -1)
+               terminal_error("fclose");
+
+       if( !ud.ram || !ud.swap ) {
+               unsigned long i;
+               fprintf(stderr, "\nCould not get memory or swap size. ");
+               fprintf(stderr, "Will not perform mem_load\n");
+               for (i = 0 ; i < THREADS ; i++) {
+                       if (threadlist[i].label == "Memload") {
+                               threadlist[i].load = 0;
+                               threadlist[i].rtload = 0;
+                       }
+               }
+       }
+}
+
+void get_logfilename(void)
+{
+       struct tm *mytm;
+       struct utsname buf;
+       time_t t;
+       int year, month, day, hours, minutes;
+
+       time(&t);
+       if (uname(&buf) == -1)
+               terminal_error("uname");
+       if (!(mytm = localtime(&t)))
+               terminal_error("localtime");
+       year = mytm->tm_year + 1900;
+       month = mytm->tm_mon + 1;
+       day = mytm->tm_mday;
+       hours = mytm->tm_hour;
+       minutes = mytm->tm_min;
+       strncpy(ud.unamer, buf.release, MAX_UNAME_LENGTH);
+
+       sprintf(ud.datestamp, "%2d%02d%02d%02d%02d",
+               year, month, day, hours, minutes);
+       snprintf(ud.logfilename, MAX_LOG_LENGTH, "%s.log", ud.unamer);
+}
+
+void start_thread(struct thread *th)
+{
+       post_sem(&th->sem.start);
+}
+
+void stop_thread(struct thread *th)
+{
+       post_sem(&th->sem.stop);
+       wait_sem(&th->sem.complete);
+
+       /* Kill the thread */
+       join_pthread(th->pthread, NULL);
+}
+
+void init_sem(sem_t *sem)
+{
+       if (sem_init(sem, 0, 0))
+               terminal_error("sem_init");
+}
+
+void init_all_sems(struct sems *s)
+{
+       /* Initialise the semaphores */
+       init_sem(&s->ready);
+       init_sem(&s->start);
+       init_sem(&s->stop);
+       init_sem(&s->complete);
+       init_sem(&s->stopchild);
+}
+
+void initialise_thread(int i)
+{
+       struct thread *th = &threadlist[i];
+
+       init_all_sems(&th->sem);
+       /* Create the threads. Yes, the (long) cast is fugly but it's safe*/
+       create_pthread(&th->pthread, NULL, emulation_thread, (void*)(long)i);
+
+       wait_sem(&th->sem.ready);
+       /*
+        * We set this pointer generically to NOT_BENCHING and set it to the
+        * benchmarked array entry only on benched threads.
+        */
+       th->dt = &th->benchmarks[NOT_BENCHING];
+       initialise_thread_data(th->dt);
+       
+}
+
+/* A pseudo-semaphore for processes using a pipe */
+void wait_on(int pype)
+{
+       int retval, buf = 0;
+
+       retval = Read(pype, &buf, sizeof(buf));
+       if (retval == 0) {
+               fprintf(stderr, "\nread returned 0\n");
+               exit (1);
+       }
+}
+
+void wakeup_with(int pype)
+{
+       int retval, buf = 1;
+
+       retval = Write(pype, &buf, sizeof(buf));
+       if (retval == 0) {
+               fprintf(stderr, "\nwrite returned 0\n");
+               exit (1);
+       }
+}
+
+void run_loadchild(int j)
+{
+       struct thread *thj;
+       thj = &threadlist[j];
+
+       set_nice(ud.load_nice);
+       initialise_thread(j);
+
+       /* Tell main we're ready */
+       wakeup_with(l2m[1]);
+
+       /* Main tells us we're ready */
+       wait_on(m2l[0]);
+       start_thread(thj);
+
+       /* Tell main we received the start and are running */
+       wakeup_with(l2m[1]);
+
+       /* Main tells us to stop */
+       wait_on(m2l[0]);
+       stop_thread(thj);
+
+       /* Tell main we've finished */
+       wakeup_with(l2m[1]);
+       exit (0);
+}
+
+void run_benchchild(int i, int j)
+{
+       struct thread *thi;
+
+       thi = &threadlist[i];
+
+       set_nice(ud.bench_nice);
+       if (ud.do_rt)
+               set_mlock();
+       initialise_thread(i);
+       /* Point the data table to the appropriate load being tested */
+       thi->dt = &thi->benchmarks[j];
+       initialise_thread_data(thi->dt);
+       if (ud.do_rt)
+               set_thread_fifo(thi->pthread, 95);
+       
+       /* Tell main we're ready */
+       wakeup_with(b2m[1]);
+
+       /* Main tells us we're ready */
+       wait_on(m2b[0]);
+       start_thread(thi);
+
+       /* Tell main we have started */
+       wakeup_with(b2m[1]);
+
+       /* Main tells us to stop */
+       wait_on(m2b[0]);
+       stop_thread(thi);
+
+       if (ud.do_rt) {
+               set_thread_normal(thi->pthread);
+               set_munlock();
+       }
+       show_latencies(thi);
+
+       /* Tell main we've finished */
+       wakeup_with(b2m[1]);
+       exit(0);
+}
+
+void bench(int i, int j)
+{
+       pid_t bench_pid, load_pid;
+
+       if ((load_pid = fork()) == -1)
+               terminal_error("fork");
+       if (!load_pid)
+               run_loadchild(j);
+
+       /* Wait for load process to be ready */
+
+       wait_on(l2m[0]);
+       if ((bench_pid = fork()) == -1)
+               terminal_error("fork");
+       if (!bench_pid)
+               run_benchchild(i, j);
+
+       /* Wait for bench process to be ready */
+       wait_on(b2m[0]);
+
+       /* 
+        * We want to be higher priority than everything to signal them to
+        * stop and we lock our memory if we can as well
+        */
+       set_fifo(99);
+       set_mlock();
+
+       /* Wakeup the load process */
+       wakeup_with(m2l[1]);
+       /* Load tells it has received the first message and is running */
+       wait_on(l2m[0]);
+
+       /* After a small delay, wake up the benched process */
+       sleep(1);
+       wakeup_with(m2b[1]);
+
+       /* Bench tells it has received the first message and is running */
+       wait_on(b2m[0]);
+       microsleep(ud.duration * 1000000);
+
+       /* Tell the benched process to stop its threads and output results */
+       wakeup_with(m2b[1]);
+
+       /* Tell the load process to stop its threads */
+       wakeup_with(m2l[1]);
+
+       /* Return to SCHED_NORMAL */
+       set_normal();
+       set_munlock();
+
+       /* Wait for load and bench processes to terminate */
+       wait_on(l2m[0]);
+       wait_on(b2m[0]);
+}
+
+void init_pipe(int *pype)
+{
+       if (pipe(pype) == -1)
+               terminal_error("pipe");
+}
+
+void init_pipes(void)
+{
+       init_pipe(m2l);
+       init_pipe(l2m);
+       init_pipe(m2b);
+       init_pipe(b2m);
+}
+
+void usage(void)
+{
+       /* Affinity commented out till working on all architectures */
+       fprintf(stderr, "interbench v " INTERBENCH_VERSION " by Con Kolivas\n");
+       fprintf(stderr, "interbench [-l <int>] [-L <int>] [-t <int] [-B <int>] [-N <int>]\n");
+       fprintf(stderr, "\t[-b] [-c] [-r] [-C <int> -I <int>] [-m <comment>]\n");
+       fprintf(stderr, "\t[-w <load type>] [-x <load type>] [-W <bench>] [-X <bench>]\n");
+       fprintf(stderr, "\t[-h\]\n\n");
+       fprintf(stderr, " -l\tUse <int> loops per sec (default: use saved benchmark)\n");
+       fprintf(stderr, " -L\tUse cpu load of <int> with burn load (default: 4)\n");
+       fprintf(stderr, " -t\tSeconds to run each benchmark (default: 30)\n");
+       fprintf(stderr, " -B\tNice the benchmarked thread to <int> (default: 0)\n");
+       fprintf(stderr, " -N\tNice the load thread to <int> (default: 0)\n");
+       //fprintf(stderr, " -u\tImitate uniprocessor\n");
+       fprintf(stderr, " -b\tBenchmark loops_per_ms even if it is already known\n");
+       fprintf(stderr, " -c\tOutput to console only (default: use console and logfile)\n");
+       fprintf(stderr, " -r\tPerform real time scheduling benchmarks (default: non-rt)\n");
+       fprintf(stderr, " -C\tUse <int> percentage cpu as a custom load (default: no custom load)\n");
+       fprintf(stderr, " -I\tUse <int> microsecond intervals for custom load (needs -C as well)\n");
+       fprintf(stderr, " -m\tAdd <comment> to the log file as a separate line\n");
+       fprintf(stderr, " -w\tAdd <load type> to the list of loads to be tested against\n");
+       fprintf(stderr, " -x\tExclude <load type> from the list of loads to be tested against\n");
+       fprintf(stderr, " -W\tAdd <bench> to the list of benchmarks to be tested\n");
+       fprintf(stderr, " -X\tExclude <bench> from the list of benchmarks to be tested\n");
+       fprintf(stderr, " -h\tShow this help\n");
+       fprintf(stderr, "\nIf run without parameters interbench will run a standard benchmark\n\n");
+}
+
+#ifdef DEBUG
+void deadchild(int crap)
+{
+       pid_t retval;
+       int status;
+
+       crap = 0;
+
+       if ((retval = waitpid(-1, &status, WNOHANG)) == -1) {
+               if (errno == ECHILD)
+                       return;
+               terminal_error("waitpid");
+       }
+       if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
+               return;
+       fprintf(stderr, "\nChild terminated abnormally ");
+       if (WIFSIGNALED(status))
+               fprintf(stderr, "with signal %d", WTERMSIG(status));
+       fprintf(stderr, "\n");
+       exit (1);
+}
+#endif
+
+int load_index(const char* loadname)
+{
+       int i;
+
+       for (i = 0 ; i < THREADS ; i++)
+               if (strcasecmp(loadname, threadlist[i].label) == 0)
+                       return i;
+       return -1;
+}
+
+inline int bit_is_on(const unsigned int mask, int index)
+{
+       return (mask & (1 << index)) != 0;
+}
+
+inline void set_bit_on(unsigned int *mask, int index)
+{
+       *mask |= (1 << index);
+}
+
+int main(int argc, char **argv)
+{
+       unsigned long custom_cpu = 0;
+       int q, i, j, affinity, benchmark = 0;
+       unsigned int selected_loads = 0;
+       unsigned int excluded_loads = 0;
+       unsigned int selected_benches = 0;
+       unsigned int excluded_benches = 0;
+       FILE *fp;
+       /* 
+        * This file stores the loops_per_ms to be reused in a filename that
+        * can't be confused
+        */
+       char *fname = "interbench.loops_per_ms";
+       char *comment = NULL;
+#ifdef DEBUG
+       feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
+       if (signal(SIGCHLD, deadchild) == SIG_ERR)
+               terminal_error("signal");
+#endif
+
+       while ((q = getopt(argc, argv, "hl:L:B:N:ut:bcnrC:I:m:w:x:W:X:")) != -1) {
+               switch (q) {
+                       case 'h':
+                               usage();
+                               return (0);
+                       case 'l':
+                               ud.loops_per_ms = atoi(optarg);
+                               break;
+                       case 't':
+                               ud.duration = atoi(optarg);
+                               break;
+                       case 'L':
+                               ud.cpu_load = atoi(optarg);
+                               break;
+                       case 'B':
+                               ud.bench_nice = atoi(optarg);
+                               break;
+                       case 'N':
+                               ud.load_nice = atoi(optarg);
+                               break;
+                       case 'u':
+                               affinity = 1;
+                               break;
+                       case 'b':
+                               benchmark = 1;
+                               break;
+                       case 'c':
+                               ud.log = 0;
+                               break;
+                       case 'r':
+                               ud.do_rt = 1;
+                               break;
+                       case 'C':
+                               custom_cpu = (unsigned long)atol(optarg);
+                               break;
+                       case 'I':
+                               ud.custom_interval = atol(optarg);
+                               break;
+                       case 'm':
+                               comment = optarg;
+                               break;
+                       case 'w':
+                               i = load_index(optarg);
+                               if (i == -1) {
+                                       fprintf(stderr, "Unknown load \"%s\"\n", optarg);
+                                       return (-2);
+                               }
+                               set_bit_on(&selected_loads, i);
+                               break;
+                       case 'x':
+                               i = load_index(optarg);
+                               if (i == -1) {
+                                       fprintf(stderr, "Unknown load \"%s\"\n", optarg);
+                                       return (-2);
+                               }
+                               set_bit_on(&excluded_loads, i);
+                               break;
+                       case 'W':
+                               i = load_index(optarg);
+                               if (i == -1) {
+                                       fprintf(stderr, "Unknown bench \"%s\"\n", optarg);
+                                       return (-2);
+                               }
+                               set_bit_on(&selected_benches, i);
+                               break;
+                       case 'X':
+                               i = load_index(optarg);
+                               if (i == -1) {
+                                       fprintf(stderr, "Unknown bench \"%s\"\n", optarg);
+                                       return (-2);
+                               }
+                               set_bit_on(&excluded_benches, i);
+                               break;
+                       default:
+                               usage();
+                               return (1);
+               }
+       }
+       argc -= optind;
+       argv += optind;
+       /* default is all loads */
+       if (selected_loads == 0)
+               selected_loads = (unsigned int)-1;
+       selected_loads &= ~excluded_loads;
+       /* default is all benches */
+       if (selected_benches == 0)
+               selected_benches = (unsigned int)-1;
+       selected_benches &= ~excluded_benches;
+
+       if (!test_fifo()) {
+               fprintf(stderr, "Unable to get SCHED_FIFO (real time scheduling).\n");
+               fprintf(stderr, "You either need to run this as root user or have support for real time RLIMITS.\n");
+               if (ud.do_rt) {
+                       fprintf(stderr, "Real time tests were requested, aborting.\n");
+                       exit (1);
+               }
+               fprintf(stderr, "Results will be unreliable.\n");
+       }
+       if (!ud.cpu_load) {
+               fprintf(stderr, "Invalid cpu load\n");
+               exit (1);
+       }
+
+       if ((custom_cpu && !ud.custom_interval) ||
+               (ud.custom_interval && !custom_cpu) ||
+               custom_cpu > 100) {
+                       fprintf(stderr, "Invalid custom values, aborting.\n");
+                       exit (1);
+       }
+
+       if (custom_cpu && ud.custom_interval) {
+               ud.custom_run = ud.custom_interval * custom_cpu / 100;
+               threadlist[CUSTOM].bench = 1;
+               threadlist[CUSTOM].load = 1;
+               threadlist[CUSTOM].rtbench = 1;
+               threadlist[CUSTOM].rtload = 1;
+       }
+
+       /*FIXME Affinity commented out till working on all architectures */
+#if 0
+       if (affinity) {
+#ifdef CPU_SET /* Current glibc expects cpu_set_t */
+               cpu_set_t cpumask;
+
+               CPU_ZERO(&cpumask);
+               CPU_SET(0, &cpumask);
+#else          /* Old glibc expects unsigned long */
+               unsigned long cpumask = 1;
+#endif
+               if (sched_setaffinity(0, sizeof(cpumask), &cpumask) == -1) {
+                       if (errno != EPERM)
+                               terminal_error("sched_setaffinity");
+                       fprintf(stderr, "could not set cpu affinity\n");
+               }
+       }
+#endif
+
+       /* Make benchmark a multiple of 10 seconds for proper range of X loads */
+       if (ud.duration % 10)
+               ud.duration += 10 - ud.duration % 10;
+
+       if (benchmark)
+               ud.loops_per_ms = 0;
+       /* 
+        * Try to get loops_per_ms from command line first, file second, and
+        * benchmark if not available.
+        */
+       if (!ud.loops_per_ms) {
+               if (benchmark)
+                       goto bench;
+               if ((fp = fopen(fname, "r"))) {
+                       fscanf(fp, "%lu", &ud.loops_per_ms);
+                       if (fclose(fp) == -1)
+                               terminal_error("fclose");
+                       if (ud.loops_per_ms) {
+                               fprintf(stderr,
+                                       "%lu loops_per_ms read from file interbench.loops_per_ms\n",
+                                       ud.loops_per_ms);
+                               goto loops_known;
+                       }
+               } else
+                       if (errno != ENOENT)
+                               terminal_error("fopen");
+bench:
+               fprintf(stderr, "loops_per_ms unknown; benchmarking...\n");
+
+               /*
+                * To get as accurate a loop as possible we time it running
+                * SCHED_FIFO if we can
+                */
+               set_fifo(99);
+               calibrate_loop();
+               set_normal();
+       } else
+               fprintf(stderr, "loops_per_ms specified from command line\n");
+
+       if (!(fp = fopen(fname, "w"))) {
+               if (errno != EACCES)    /* No write access is not terminal */
+                       terminal_error("fopen");
+               fprintf(stderr, "Unable to write to file interbench.loops_per_ms\n");
+               goto loops_known;
+       }
+       fprintf(fp, "%lu", ud.loops_per_ms);
+       fprintf(stderr, "%lu loops_per_ms saved to file interbench.loops_per_ms\n",
+               ud.loops_per_ms);
+       if (fclose(fp) == -1)
+               terminal_error("fclose");
+
+loops_known:
+       get_ram();
+       get_logfilename();
+       create_read_file();
+       init_pipes();
+
+       if (ud.log && !(ud.logfile = fopen(ud.logfilename, "a"))) {
+               if (errno != EACCES)
+                       terminal_error("fopen");
+               fprintf(stderr, "Unable to write to logfile\n");
+               ud.log = 0;
+       }
+       log_output("\n");
+       log_output("Using %lu loops per ms, running every load for %d seconds\n",
+               ud.loops_per_ms, ud.duration);
+       log_output("Benchmarking kernel %s at datestamp %s\n",
+               ud.unamer, ud.datestamp);
+       if (comment)
+               log_output("Comment: %s\n", comment);
+       log_output("\n");
+
+       for (i = 0 ; i < THREADS ; i++)
+               threadlist[i].threadno = i;
+
+       for (i = 0 ; i < THREADS ; i++) {
+               struct thread *thi = &threadlist[i];
+               int *benchme;
+
+               if (ud.do_rt)
+                       benchme = &threadlist[i].rtbench;
+               else
+                       benchme = &threadlist[i].bench;
+
+               if (!*benchme || !bit_is_on(selected_benches, i))
+                       continue;
+
+               log_output("--- Benchmarking simulated cpu of %s ", threadlist[i].label);
+               if (ud.do_rt)
+                       log_output("real time ");
+               else if (ud.bench_nice)
+                       log_output("nice %d ", ud.bench_nice);
+               log_output("in the presence of simulated ");
+               if (ud.load_nice)
+                       log_output("nice %d ", ud.load_nice);
+               log_output("---\n");
+
+               log_output("Load");
+               if (ud.do_rt)
+                       log_output("\tLatency +/- SD (us)");
+               else
+                       log_output("\tLatency +/- SD (ms)");
+               log_output("  Max Latency ");
+               log_output("  %% Desired CPU");
+               if (!thi->nodeadlines)
+                       log_output("  %% Deadlines Met");
+               log_output("\n");
+
+               for (j = 0 ; j < THREADS ; j++) {
+                       struct thread *thj = &threadlist[j];
+
+                       if (j == i || !bit_is_on(selected_loads, j) ||
+                               (!threadlist[j].load && !ud.do_rt) ||
+                               (!threadlist[j].rtload && ud.do_rt))
+                                       continue;
+                       log_output("%s\t", thj->label);
+                       sync_flush();
+                       bench(i, j);
+               }
+               log_output("\n");
+       }
+       log_output("\n");
+       if (ud.log)
+               fclose(ud.logfile);
+
+       return 0;
+}
diff --git a/test/interbench/interbench.h b/test/interbench/interbench.h
new file mode 100644 (file)
index 0000000..f94ad5d
--- /dev/null
@@ -0,0 +1,56 @@
+/* Interbench.h */
+#ifndef INTERBENCH_H
+#define INTERBENCH_H
+
+extern void *hackbench_thread(void *t);
+extern void terminal_error(const char *name);
+extern inline void post_sem(sem_t *s);
+extern inline void wait_sem(sem_t *s);
+extern inline int trywait_sem(sem_t *s);
+extern inline ssize_t Read(int fd, void *buf, size_t count);
+
+#define THREADS                13      /* The total number of different loads */
+
+struct sems {
+       sem_t ready;
+       sem_t start;
+       sem_t stop;
+       sem_t complete;
+       sem_t stopchild;
+};
+
+struct tk_thread {
+       struct sems sem;
+       unsigned long sleep_interval;
+       unsigned long slept_interval;
+};
+
+struct data_table {
+       unsigned long long total_latency;
+       unsigned long long sum_latency_squared;
+       unsigned long max_latency;
+       unsigned long nr_samples;
+       unsigned long deadlines_met;
+       unsigned long missed_deadlines;
+       unsigned long long missed_burns;
+       unsigned long long achieved_burns;
+};
+
+struct thread {
+       void (*name)(struct thread *);
+       char *label;
+       int bench;              /* This thread is suitable for benchmarking */
+       int rtbench;            /* Suitable for real time benchmarking */
+       int load;               /* Suitable as a background load */
+       int rtload;             /* Suitable as a background load for rt benches */
+       int nodeadlines;        /* Deadlines_met are meaningless for this load */
+       unsigned long decasecond_deadlines;     /* Expected deadlines / 10s */
+       pthread_t pthread;
+       pthread_t tk_pthread;
+       struct sems sem;
+       struct data_table benchmarks[THREADS + 1], *dt;
+       struct tk_thread tkthread;
+       long threadno;
+};
+extern struct thread hackthread;
+#endif
diff --git a/test/interbench/readme b/test/interbench/readme
new file mode 100644 (file)
index 0000000..1cf48c1
--- /dev/null
@@ -0,0 +1,240 @@
+       Interbench - The Linux Interactivity Benchmark
+
+
+       Introduction
+
+This benchmark application is designed to benchmark interactivity in Linux. See
+the file readme.interactivity for a brief definition. 
+
+It is designed to measure the effect of changes in Linux kernel design or system
+configuration changes such as cpu, I/O scheduler and filesystem changes and
+options. With careful benchmarking, different hardware can be compared.
+
+
+       What does it do?
+
+It is designed to emulate the cpu scheduling behaviour of interactive tasks and
+measure their scheduling latency and jitter. It does this with the tasks on
+their own and then in the presence of various background loads, both with
+configurable nice levels and the benchmarked tasks can be real time.
+
+
+       How does it work?
+
+First it benchmarks how best to reproduce a fixed percentage of cpu usage on the
+machine currently being used for the benchmark. It saves this to a file and then
+uses this for all subsequent runs to keep the emulation of cpu usage constant.
+
+It runs a real time high priority timing thread that wakes up the thread or
+threads of the simulated interactive tasks and then measures the latency in the
+time taken to schedule. As there is no accurate timer driven scheduling in linux
+the timing thread sleeps as accurately as linux kernel supports, and latency is
+considered as the time from this sleep till the simulated task gets scheduled.
+
+Each benchmarked simulation runs as a separate process with its own threads,
+and the background load (if any) also runs as a separate process.
+
+
+       What interactive tasks are simulated and how?
+
+X:
+X is simulated as a thread that uses a variable amount of cpu ranging from 0 to
+100%. This simulates an idle gui where a window is grabbed and then dragged
+across the screen.
+
+Audio:
+Audio is simulated as a thread that tries to run at 50ms intervals that then
+requires 5% cpu. This behaviour ignores any caching that would normally be done
+by well designed audio applications, but has been seen as the interval used to
+write to audio cards by a popular linux audio player. It also ignores any of the
+effects of different audio drivers and audio cards. Audio is also benchmarked
+running SCHED_FIFO if the real time benchmarking option is used.
+
+Video:
+Video is simulated as a thread that tries to receive cpu 60 times per second
+and uses 40% cpu. This would be quite a demanding video playback at 60fps. Like
+the audio simulator it ignores caching, drivers and video cards. As per audio,
+video is benchmarked with the real time option.
+
+Gaming:
+The cpu usage behind gaming is not at all interactive, yet games clearly are
+intended for interactive usage. This load simply uses as much cpu as it can
+get. It does not return deadlines met as there are no deadlines with an
+unlocked frame rate in a game. This does not accurately emulate a 3d game
+which is gpu bound (limited purely by the graphics card), only a cpu bound
+one.
+
+Custom:
+This load will allow you to specify your own combination of cpu percentage and
+intervals if you have a specific workload you are interested in and know the
+cpu usage and frame rate of it on the hardware you are testing.
+
+
+       What loads are simulated?
+
+None:
+Otherwise idle system.
+
+Video:
+The video simulation thread is also used as a background load.
+
+X:
+The X simulation thread is used as a load.
+
+Burn:
+A configurable number of threads fully cpu bound (4 by default).
+
+Write:
+A streaming write to disk repeatedly of a file the size of physical ram.
+
+Read:
+Repeatedly reading a file from disk the size of physical ram (to avoid any
+caching effects).
+
+Compile:
+Simulating a heavy 'make -j4' compilation by running Burn, Write and Read
+concurrently.
+
+Memload:
+Simulating heavy memory and swap pressure by repeatedly accessing 110% of
+available ram and moving it around and freeing it. You need to have some
+swap enabled due to the nature of this load, and if it detects no swap this
+load is disabled.
+
+Hack:
+This repeatedly runs the benchmarking program "hackbench" as 'hackbench 50'.
+This is suggested as a real time load only but because of how extreme this
+load is it is not unusual for an out-of-memory kill to occur which will
+invalidate any data you get. For this reason it is disabled by default.
+
+Custom:
+The custom simulation is used as a load.
+
+
+       What is measured and what does it mean?
+
+1. The average scheduling latency (time to requesting cpu till actually getting
+it) of deadlines met during the test period. 
+2. The scheduling jitter is represented by calculating the standard deviation
+of the latency
+3. The maximum latency seen during the test period
+4. Percentage of desired cpu
+5. Percentage of deadlines met.
+
+This data is output to console and saved to a file which is stamped with the
+kernel name and date. See sample.log.
+
+       Sample:
+--- Benchmarking simulated cpu of X in the presence of simulated ---
+Load   Latency +/- SD (ms)  Max Latency   % Desired CPU  % Deadlines Met
+None     0.495 +/- 0.495         45             100             96
+Video     11.7 +/- 11.7        1815            89.6           62.7
+Burn      27.9 +/- 28.1        3335            78.5             44
+Write     4.02 +/- 4.03         372              97           78.7
+Read      1.09 +/- 1.09         158            99.7             88
+Compile           28.8 +/- 28.8        3351            78.2           43.7
+Memload           2.81 +/- 2.81         187            98.7             85
+
+What can be seen here is that never during this test run were all the so called
+deadlines met by the X simulator, although all the desired cpu was achieved
+under no load. In X terms this means that every bit of window movement was
+drawn while moving the window, but some were delayed and there was enough time
+to catch up before the next deadline. In the 'Burn' column we can see that only
+44% of the deadlines were met, and only 78.5% of the desired cpu was achieved.
+This means that some deadlines were so late (%deadlines met was low) that some
+redraws were dropped entirely to catch up. In X terms this would translate into
+jerky movement, in audio it would be a skip, and in video it would be a dropped
+frame. Note that despite the massive maximum latency of >3seconds, the average
+latency is still less than 30ms. This is because redraws are dropped in order
+to catch up usually by these sorts of applications.
+
+
+       What is relevant in the data?
+
+The results pessimise quite a lot what happens in real world terms because they
+ignore the reality of buffering, but this allows us to pick up subtle 
+differences more readily. In terms of what would be noticed by the end user,
+dropping deadlines would make noticable clicks in audio, subtle visible frame
+time delays in video, and loss of "smooth" movement in X. Dropping desired cpu
+would be much more noticeable with audio skips, missed video frames or jerks
+in window movement under X. The magnitude of these would be best represented by
+the maximum latency. When the deadlines are actually met, the average latency
+represents how "smooth" it would look. Average humans' limit of perception for
+jitter is in the order of 7ms. Trained audio observers might notice much less.
+
+
+       How to use it?
+
+In response to critisicm of difficulty in setting up my previous benchmark, 
+contest, I've made this as simple as possible.
+
+       Short version:
+make
+./interbench
+
+Please read the long version before submitting results!
+
+       Longer version:
+Build with 'make'. It is a single executable once built so if you desire to
+install it simply copy the interbench binary wherever you like.
+
+To get good reproducible data from it you should boot into runlevel one so
+that nothing else is running on the machine. All power saving (cpu throttling,
+cpu frequency modifications) must be disabled on the first run to get an
+accurate measurement for cpu usage. You may enable them later if you are
+benchmarking their effect on interactivity on that machine. Root is almost
+mandatory for this benchmark, or real time privileges at the very least. You
+need free disk space in the directory it is being run in the order of 2* your
+physical ram for the disk loads. A default run in v0.21 takes about 15
+minutes to complete, longer if your disk is slow.
+
+As the benchmark bases the work it does on the speed of the hardware the
+results from different hardware can not be directly compared. However changes
+of kernels, filesystem and options can be compared. To do a comparison of
+different cpus and keep the workload constant, using the -l option and
+passing the value of "loops_per_ms" from the first hardware tested will keep
+the number of cpu cycles fairly constant allowing some comparison. Future
+versions may add the option of setting the amount of disk throughput etc.
+
+
+Command line options supported:
+interbench [-l <int>] [-L <int>] [-t <int] [-B <int>] [-N <int>]
+        [-b] [-c] [-r] [-C <int> -I <int>] [-m <comment>]
+        [-w <load type>] [-x <load type>] [-W <bench>] [-X <bench>]
+        [-h]
+
+ -l     Use <int> loops per sec (default: use saved benchmark)
+ -L     Use cpu load of <int> with burn load (default: 4)
+ -t     Seconds to run each benchmark (default: 30)
+ -B     Nice the benchmarked thread to <int> (default: 0)
+ -N     Nice the load thread to <int> (default: 0)
+ -b     Benchmark loops_per_ms even if it is already known
+ -c     Output to console only (default: use console and logfile)
+ -r     Perform real time scheduling benchmarks (default: non-rt)
+ -C     Use <int> percentage cpu as a custom load (default: no custom load)
+ -I     Use <int> microsecond intervals for custom load (needs -C as well)
+ -m     Add <comment> to the log file as a separate line
+ -w     Add <load type> to the list of loads to be tested against
+ -x     Exclude <load type> from the list of loads to be tested against
+ -W     Add <bench> to the list of benchmarks to be tested
+ -X     Exclude <bench> from the list of benchmarks to be tested
+ -h     Show help
+
+There is one hidden option which is not supported by default, -u
+which emulates a uniprocessor when run on an smp machine. The support for cpu
+affinity is not built in by default because there are multiple versions of
+the sched_setaffinity call in glibc that not only accept different variable
+types but across architectures take different numbers of arguments. For x86
+support you can change the '#if 0' in interbench.c to '#if 1' to enable the
+affinity support to be built in. The function on x86_64 for those very keen
+does not have the sizeof argument.
+
+
+Thanks:
+For help from Zwane Mwaikambo, Bert Hubert, Seth Arnold, Rik Van Riel,
+Nicholas Miell, John Levon, Miguel Freitas and Peter Williams.
+Aggelos Economopoulos for contest code, Bob Matthews for irman (mem_load)
+code, Rusty Russell for hackbench code and Julien Valroff for manpage.
+
+Sat Mar 4 12:11:34 2006
+Con Kolivas < kernel at kolivas dot org >
diff --git a/test/interbench/readme.interactivity b/test/interbench/readme.interactivity
new file mode 100644 (file)
index 0000000..d3eb4ef
--- /dev/null
@@ -0,0 +1,25 @@
+       Interactivity, what is it?
+
+There has been a lot of talk about what makes up a nice feeling desktop under
+linux. It comes down to two different but intimately related parameters which
+are not well defined. We often use the terms responsiveness and interactivity
+in the same sentence, but I'd like to separate the two. As there is no formal
+definition I prefer to define them as such:
+
+Responsiveness: The rate at which your workloads can proceed under different
+       load conditions.
+
+Interactivity: The scheduling latency and jitter present in tasks where the user
+       would notice a palpable deterioration under different load conditions.
+
+Responsiveness would allow you to continue using your machine without too much
+interruption to your work, whereas interactivity would allow you to play audio
+or video without any dropouts, or drag a gui window across the screen and have
+it render smoothly across the screen without jerks .
+
+Contest was a benchmark originally written by me to test system responsiveness,
+and interbench is a benchmark I wrote as a sequel to contest to test
+interactivity.
+
+Con Kolivas
+Mon Jul 11 17:29:21 2005