Merge the iowatcher repository
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2436e34
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+.depend
+*.o
+blkparse
+blktrace
+blkrawverify
+blkiomon
+btreplay/btrecord
+btreplay/btreplay
+doc/.depend
+verify_blkparse
+btt/btt
+btt/doc/btt.pdf
+doc/blktrace.pdf
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..5b6e7c6
--- /dev/null
+++ b/COPYING
@@ -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.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <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/Makefile b/Makefile
new file mode 100644
index 0000000..623b84b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,90 @@
+CC	= gcc
+CFLAGS	= -Wall -O2 -g -W
+ALL_CFLAGS = $(CFLAGS) -D_GNU_SOURCE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
+PROGS	= blkparse blktrace verify_blkparse blkrawverify blkiomon
+LIBS	= -lpthread
+SCRIPTS	= btrace
+
+ALL = $(PROGS) $(SCRIPTS) btt/btt btreplay/btrecord btreplay/btreplay \
+      btt/bno_plot.py
+
+all: $(ALL)
+
+btt/btt:
+	$(MAKE) -C btt
+
+btreplay/btrecord:
+	$(MAKE) -C btreplay
+
+btreplay/btreplay:
+	$(MAKE) -C btreplay
+
+%.o: %.c
+	$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+
+blkparse: blkparse.o blkparse_fmt.o rbtree.o act_mask.o
+	$(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^)
+
+blktrace: blktrace.o act_mask.o
+	$(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^) $(LIBS)
+
+verify_blkparse: verify_blkparse.o
+	$(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^)
+
+blkrawverify: blkrawverify.o
+	$(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^)
+
+blkiomon: blkiomon.o rbtree.o
+	$(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^) $(LIBS) -lrt
+
+$(PROGS): | depend
+
+docs:
+	$(MAKE) -C doc all
+	$(MAKE) -C btt docs
+	$(MAKE) -C btreplay docs
+
+docsclean:
+	$(MAKE) -C doc clean
+	$(MAKE) -C btt clean
+	$(MAKE) -C btreplay clean
+
+depend:
+	@$(CC) -MM $(ALL_CFLAGS) *.c 1> .depend
+
+INSTALL = install
+prefix = /usr/local
+bindir = $(prefix)/bin
+mandir = $(prefix)/man
+RPMBUILD = rpmbuild
+TAR = tar
+
+export prefix INSTALL TAR
+
+dist: btrace.spec
+	git-tar-tree HEAD btrace-1.0 > btrace-1.0.tar
+	@mkdir -p btrace-1.0
+	@cp btrace.spec btrace-1.0
+	$(TAR) rf btrace-1.0.tar btrace-1.0/btrace.spec
+	@rm -rf btrace-1.0
+	@bzip2 btrace-1.0.tar
+
+rpm: dist
+	$(RPMBUILD) -ta btrace-1.0.tar.bz2
+
+clean: docsclean
+	-rm -f *.o $(PROGS) .depend btrace-1.0.tar.bz2
+	$(MAKE) -C btt clean
+	$(MAKE) -C btreplay clean
+
+install: all
+	$(INSTALL) -m 755 -d $(DESTDIR)$(bindir)
+	$(INSTALL) -m 755 -d $(DESTDIR)$(mandir)/man1
+	$(INSTALL) -m 755 -d $(DESTDIR)$(mandir)/man8
+	$(INSTALL) -m 755 $(ALL) $(DESTDIR)$(bindir)
+	$(INSTALL) -m 644 doc/*.1 $(DESTDIR)$(mandir)/man1
+	$(INSTALL) -m 644 doc/*.8 $(DESTDIR)$(mandir)/man8
+
+ifneq ($(wildcard .depend),)
+include .depend
+endif
diff --git a/README b/README
new file mode 100644
index 0000000..f52f48d
--- /dev/null
+++ b/README
@@ -0,0 +1,183 @@
+Block IO Tracing
+----------------
+
+Written by Jens Axboe <axboe@kernel.dk> (initial version and kernel support),
+Alan D. Brunelle (threading and splitup into two seperate programs),
+Nathan Scott <nathans@sgi.com> (bug fixes, process names, multiple devices)
+Also thanks to Tom Zanussi <zanussi@us.ibm.com> for good input and
+patches.
+
+
+Requirements
+------------
+
+blktrace was integrated into the mainline kernel between 2.6.16 and 2.6.17-rc1.
+The target trace needs to run on a kernel at least that new.
+
+git://git.kernel.dk/blktrace.git
+
+If you don't have git, you can get hourly snapshots from:
+
+http://brick.kernel.dk/snaps/
+
+The snapshots include the full git object database as well. kernel.org has
+excessively long mirror times, so if you have git installed, you can pull
+the master tree from:
+
+git://git.kernel.dk/blktrace.git
+
+For browsing the repo over http and viewing history etc, you can direct
+your browser to:
+
+http://git.kernel.dk/
+
+
+Usage
+-----
+
+$ blktrace -d <dev> [ -r debug_path ] [ -o output ] [ -k ] [ -w time ]
+		    [ -a action ] [ -A action mask ]
+
+	-d Use specified device. May also be given last after options.
+	-r Path to mounted debugfs, defaults to /sys/kernel/debug.
+	-o File(s) to send output to.
+	-D Directory to prepend to output file names.
+	-k Kill running trace.
+	-w Stop after defined time, in seconds.
+	-a Only trace specific actions (use more -a options to add actions).
+	   Available actions are:
+
+		READ
+		WRITE
+		BARRIER
+		SYNC
+		QUEUE
+		REQUEUE
+		ISSUE
+		COMPLETE
+		FS
+		PC
+
+	-A Give the trace mask directly as a number.
+
+	-b Sub buffer size in KiB.
+	-n Number of sub buffers.
+	-l Run in network listen mode (blktrace server)
+	-h Run in network client mode, connecting to the given host
+	-p Network port to use (default 8462)
+	-s Disable network client use of sendfile() to transfer data
+	-V Print program version info.
+
+$ blkparse -i <input> [ -o <output> ] [ -b rb_batch ] [ -s ] [ -t ] [ -q ]
+		      [ -w start:stop ] [ -f output format ] [ -F format spec ]
+		      [ -d <binary> ]
+
+	-i Input file containing trace data, or '-' for stdin.
+	-D Directory to prepend to input file names.
+	-o Output file. If not given, output is stdout.
+	-b stdin read batching.
+	-s Show per-program io statistics.
+	-h Hash processes by name, not pid.
+	-t Track individual ios. Will tell you the time a request took to
+	   get queued, to get dispatched, and to get completed.
+	-q Quiet. Don't display any stats at the end of the trace.
+	-w Only parse data between the given time interval in seconds. If
+	   'start' isn't given, blkparse defaults the start time to 0.
+	-d Dump sorted data in binary format
+	-f Output format. Customize the output format. The format field
+	   identifiers are:
+
+		%a	- Action
+		%c	- CPU ID
+		%C	- Task command (process) name
+		%d	- Direction (r/w)
+		%D	- Device number
+		%e	- Error number
+		%M	- Major
+		%m	- Minor
+		%N	- Number of bytes
+		%n	- Number of sectors
+		%p	- PID
+		%P	- PDU
+		%s	- Sequence number
+		%S	- Sector number
+		%t	- Time (wallclock - nanoseconds)
+		%T	- Time (wallclock - seconds)
+		%u	- Time (processing - microseconds)
+		%U	- Unplug depth
+
+	-F Format specification. The individual specifiers are:
+
+		A	- Remap
+		B	- Bounce
+		C	- Complete
+		D	- Issue
+		M	- Back merge
+		F	- Front merge
+		G	- Get request
+		I	- Insert
+		P	- Plug
+		Q	- Queue
+		R	- Requeue
+		S	- Sleep requests
+		T	- Unplug timer
+		U	- Unplug IO
+		W	- Bounce
+		X	- Split
+
+	-v More verbose for marginal errors.
+	-V Print program version info.
+
+$ verify_blkparse filename
+
+	Verifies an output file from blkparse. All it does is check if
+	the events in the file are correctly time ordered. If an entry
+	is found that isn't ordered, it's dumped to stdout.
+
+$ blkrawverify <dev> [<dev>...]
+
+	The blkrawverify utility can be used to verify data retrieved
+	via blktrace. It will check for valid event formats, forward
+	progressing sequence numbers and time stamps, also does reasonable
+	checks for other potential issues within invidividual events.
+
+	Errors found will be tracked in <dev>.verify.out.
+
+If you want to do live tracing, you can pipe the data between blktrace
+and blkparse:
+
+% blktrace -d <device> -o - | blkparse -i -
+
+This has a small risk of displaying some traces a little out of sync, since
+it will do batch sorts of input events. Similarly, you can do traces over
+the network. The network 'server' must run:
+
+% blktrace -l
+
+to listen to incoming blktrace connections, while the client should use
+
+% blktrace -d /dev/sda -h <server hostname>
+
+to connect and transfer data over the network.
+
+
+Documentation
+-------------
+
+A users guide is distributed with the source. It is in latex, a
+'make docs' will build a PDF in doc/. You need tetex and latex installed
+to build the document.
+
+
+Resources
+---------
+
+vger hosts a mailing list dedicated to btrace discussion and development.
+The list is called linux-btrace@vger.kernel.org, subscribe by sending
+a mail to majordomo@vger.kernel.org with 'subscribe linux-btrace' in
+the mail body.
+
+
+
+2006-09-05, Jens Axboe <axboe@kernel.dk>
+
diff --git a/act_mask.c b/act_mask.c
new file mode 100644
index 0000000..8f1b8d7
--- /dev/null
+++ b/act_mask.c
@@ -0,0 +1,48 @@
+#include <strings.h>
+#include "blktrace.h"
+
+#define DECLARE_MASK_MAP(mask)          { BLK_TC_##mask, #mask, "BLK_TC_"#mask }
+#define COMPARE_MASK_MAP(mmp, str)                                      \
+        (!strcasecmp((mmp)->short_form, (str)) ||                      \
+         !strcasecmp((mmp)->long_form, (str)))
+
+struct mask_map {
+	int mask;
+	char *short_form;
+	char *long_form;
+};
+
+static struct mask_map mask_maps[] = {
+	DECLARE_MASK_MAP(READ),
+	DECLARE_MASK_MAP(WRITE),
+	DECLARE_MASK_MAP(FLUSH),
+	DECLARE_MASK_MAP(SYNC),
+	DECLARE_MASK_MAP(QUEUE),
+	DECLARE_MASK_MAP(REQUEUE),
+	DECLARE_MASK_MAP(ISSUE),
+	DECLARE_MASK_MAP(COMPLETE),
+	DECLARE_MASK_MAP(FS),
+	DECLARE_MASK_MAP(PC),
+	DECLARE_MASK_MAP(NOTIFY),
+	DECLARE_MASK_MAP(AHEAD),
+	DECLARE_MASK_MAP(META),
+	DECLARE_MASK_MAP(DISCARD),
+	DECLARE_MASK_MAP(DRV_DATA),
+	DECLARE_MASK_MAP(FUA),
+};
+
+int find_mask_map(char *string)
+{
+	unsigned int i;
+
+	for (i = 0; i < sizeof(mask_maps)/sizeof(mask_maps[0]); i++)
+		if (COMPARE_MASK_MAP(&mask_maps[i], string))
+			return mask_maps[i].mask;
+
+	return -1;
+}
+
+int valid_act_opt(int x)
+{
+	return (1 <= x) && (x < (1 << BLK_TC_SHIFT));
+}
diff --git a/blkiomon.c b/blkiomon.c
new file mode 100644
index 0000000..a895f65
--- /dev/null
+++ b/blkiomon.c
@@ -0,0 +1,757 @@
+/*
+ * I/O monitor based on block queue trace data
+ *
+ * Copyright IBM Corp. 2008
+ *
+ * Author(s): Martin Peschke <mp3@de.ibm.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <getopt.h>
+#include <errno.h>
+#include <locale.h>
+#include <libgen.h>
+#include <sys/msg.h>
+#include <pthread.h>
+#include <time.h>
+
+#include "blktrace.h"
+#include "rbtree.h"
+#include "jhash.h"
+#include "blkiomon.h"
+
+struct trace {
+	struct blk_io_trace bit;
+	struct rb_node node;
+	struct trace *next;
+	long sequence;
+};
+
+struct rb_search {
+	struct rb_node **node_ptr;
+	struct rb_node *parent;
+};
+
+struct dstat_msg {
+	long mtype;
+	struct blkiomon_stat stat;
+};
+
+struct dstat {
+	struct dstat_msg msg;
+	struct rb_node node;
+	struct dstat *next;
+};
+
+struct output {
+	char *fn;
+	FILE *fp;
+	char *buf;
+	int pipe;
+};
+
+static char blkiomon_version[] = "0.3";
+
+static FILE *ifp;
+static int interval = -1;
+
+static struct trace *vacant_traces_list = NULL;
+static int vacant_traces = 0;
+
+#define TRACE_HASH_SIZE 128
+struct trace *thash[TRACE_HASH_SIZE] = {};
+
+static struct dstat *vacant_dstats_list = NULL;
+static struct rb_root dstat_tree[2] = { RB_ROOT, RB_ROOT };
+static struct dstat *dstat_list[2] = {};
+static int dstat_curr = 0;
+
+static struct output drvdata, human, binary, debug;
+
+static char *msg_q_name = NULL;
+static int msg_q_id = -1, msg_q = -1;
+static long msg_id = -1;
+
+static pthread_t interval_thread;
+static pthread_mutex_t dstat_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+int data_is_native = -1;
+
+static int up = 1;
+
+/* debugging */
+static long leftover = 0, driverdata = 0, match = 0, mismatch = 0, sequence = 0;
+
+static void dump_bit(struct trace *t, const char *descr)
+{
+	struct blk_io_trace *bit = &t->bit;
+
+	if (!debug.fn)
+		return;
+
+	fprintf(debug.fp, "--- %s ---\n", descr);
+	fprintf(debug.fp, "magic    %16d\n", bit->magic);
+	fprintf(debug.fp, "sequence %16d\n", bit->sequence);
+	fprintf(debug.fp, "time     %16ld\n", (unsigned long)bit->time);
+	fprintf(debug.fp, "sector   %16ld\n", (unsigned long)bit->sector);
+	fprintf(debug.fp, "bytes    %16d\n", bit->bytes);
+	fprintf(debug.fp, "action   %16x\n", bit->action);
+	fprintf(debug.fp, "pid      %16d\n", bit->pid);
+	fprintf(debug.fp, "device   %16d\n", bit->device);
+	fprintf(debug.fp, "cpu      %16d\n", bit->cpu);
+	fprintf(debug.fp, "error    %16d\n", bit->error);
+	fprintf(debug.fp, "pdu_len  %16d\n", bit->pdu_len);
+
+	fprintf(debug.fp, "order    %16ld\n", t->sequence);
+}
+
+static void dump_bits(struct trace *t1, struct trace *t2, const char *descr)
+{
+	struct blk_io_trace *bit1 = &t1->bit;
+	struct blk_io_trace *bit2 = &t2->bit;
+
+	if (!debug.fn)
+		return;
+
+	fprintf(debug.fp, "--- %s ---\n", descr);
+	fprintf(debug.fp, "magic    %16d %16d\n", bit1->magic, bit2->magic);
+	fprintf(debug.fp, "sequence %16d %16d\n",
+		bit1->sequence, bit2->sequence);
+	fprintf(debug.fp, "time     %16ld %16ld\n",
+		(unsigned long)bit1->time, (unsigned long)bit2->time);
+	fprintf(debug.fp, "sector   %16ld %16ld\n",
+		(unsigned long)bit1->sector, (unsigned long)bit2->sector);
+	fprintf(debug.fp, "bytes    %16d %16d\n", bit1->bytes, bit2->bytes);
+	fprintf(debug.fp, "action   %16x %16x\n", bit1->action, bit2->action);
+	fprintf(debug.fp, "pid      %16d %16d\n", bit1->pid, bit2->pid);
+	fprintf(debug.fp, "device   %16d %16d\n", bit1->device, bit2->device);
+	fprintf(debug.fp, "cpu      %16d %16d\n", bit1->cpu, bit2->cpu);
+	fprintf(debug.fp, "error    %16d %16d\n", bit1->error, bit2->error);
+	fprintf(debug.fp, "pdu_len  %16d %16d\n", bit1->pdu_len, bit2->pdu_len);
+
+	fprintf(debug.fp, "order    %16ld %16ld\n", t1->sequence, t2->sequence);
+}
+
+static struct dstat *blkiomon_alloc_dstat(void)
+{
+	struct dstat *dstat;
+
+	if (vacant_dstats_list) {
+		dstat = vacant_dstats_list;
+		vacant_dstats_list = dstat->next;
+	} else
+		dstat = malloc(sizeof(*dstat));
+	if (!dstat) {
+		fprintf(stderr,
+			"blkiomon: could not allocate device statistic");
+		return NULL;
+	}
+
+	blkiomon_stat_init(&dstat->msg.stat);
+	return dstat;
+}
+
+static struct dstat *blkiomon_find_dstat(struct rb_search *search, __u32 device)
+{
+	struct rb_node **p = &(dstat_tree[dstat_curr].rb_node);
+	struct rb_node *parent = NULL;
+	struct dstat *dstat;
+
+	while (*p) {
+		parent = *p;
+
+		dstat = rb_entry(parent, struct dstat, node);
+
+		if (dstat->msg.stat.device < device)
+			p = &(*p)->rb_left;
+		else if (dstat->msg.stat.device > device)
+			p = &(*p)->rb_right;
+		else
+			return dstat;
+	}
+	search->node_ptr = p;
+	search->parent = parent;
+	return NULL;
+}
+
+static struct dstat *blkiomon_get_dstat(__u32 device)
+{
+	struct dstat *dstat;
+	struct rb_search search;
+
+	pthread_mutex_lock(&dstat_mutex);
+
+	dstat = blkiomon_find_dstat(&search, device);
+	if (dstat)
+		goto out;
+
+	dstat = blkiomon_alloc_dstat();
+	if (!dstat)
+		goto out;
+
+	dstat->msg.stat.device = device;
+
+	rb_link_node(&dstat->node, search.parent, search.node_ptr);
+	rb_insert_color(&dstat->node, &dstat_tree[dstat_curr]);
+
+	dstat->next = dstat_list[dstat_curr];
+	dstat_list[dstat_curr] = dstat;
+
+out:
+	pthread_mutex_unlock(&dstat_mutex);
+	return dstat;
+}
+
+static int blkiomon_output_msg_q(struct dstat *dstat)
+{
+	if (!msg_q_name)
+		return 0;
+
+	dstat->msg.mtype = msg_id;
+	return msgsnd(msg_q, &dstat->msg, sizeof(struct blkiomon_stat), 0);
+}
+
+static int blkiomon_output_binary(struct dstat *dstat)
+{
+	struct blkiomon_stat *p = &dstat->msg.stat;
+
+	if (!binary.fn)
+		return 0;
+
+	if (fwrite(p, sizeof(*p), 1, binary.fp) != 1)
+		goto failed;
+	if (binary.pipe && fflush(binary.fp))
+		goto failed;
+	return 0;
+
+failed:
+	fprintf(stderr, "blkiomon: could not write to %s\n", binary.fn);
+	fclose(binary.fp);
+	binary.fn = NULL;
+	return 1;
+}
+
+static struct dstat *blkiomon_output(struct dstat *head, struct timespec *ts)
+{
+	struct dstat *dstat, *tail = NULL;
+
+	for (dstat = head; dstat; dstat = dstat->next) {
+		dstat->msg.stat.time = ts->tv_sec;
+		blkiomon_stat_print(human.fp, &dstat->msg.stat);
+		blkiomon_stat_to_be(&dstat->msg.stat);
+		blkiomon_output_binary(dstat);
+		blkiomon_output_msg_q(dstat);
+		tail = dstat;
+	}
+	return tail;
+}
+
+static void *blkiomon_interval(void *data)
+{
+	struct timespec wake, r;
+	struct dstat *head, *tail;
+	int finished;
+
+	clock_gettime(CLOCK_REALTIME, &wake);
+
+	while (1) {
+		wake.tv_sec += interval;
+		if (clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &wake, &r)) {
+			fprintf(stderr, "blkiomon: interrupted sleep");
+			continue;
+		}
+
+		/* grab tree and make data gatherer build up another tree */
+		pthread_mutex_lock(&dstat_mutex);
+		finished = dstat_curr;
+		dstat_curr = dstat_curr ? 0 : 1;
+		pthread_mutex_unlock(&dstat_mutex);
+
+		head = dstat_list[finished];
+		if (!head)
+			continue;
+		dstat_list[finished] = NULL;
+		dstat_tree[finished] = RB_ROOT;
+		tail = blkiomon_output(head, &wake);
+
+		pthread_mutex_lock(&dstat_mutex);
+		tail->next = vacant_dstats_list;
+		vacant_dstats_list = head;
+		pthread_mutex_unlock(&dstat_mutex);
+	}
+	return data;
+}
+
+#define BLK_DATADIR(a) (((a) >> BLK_TC_SHIFT) & (BLK_TC_READ | BLK_TC_WRITE))
+
+static int blkiomon_account(struct blk_io_trace *bit_d,
+			    struct blk_io_trace *bit_c)
+{
+	struct dstat *dstat;
+	struct blkiomon_stat *p;
+	__u64 d2c = (bit_c->time - bit_d->time) / 1000; /* ns -> us */
+	__u32 size = bit_d->bytes;
+	__u64 thrput = size * 1000 / d2c;
+
+	dstat = blkiomon_get_dstat(bit_d->device);
+	if (!dstat)
+		return 1;
+	p = &dstat->msg.stat;
+
+	if (BLK_DATADIR(bit_c->action) & BLK_TC_READ) {
+		minmax_account(&p->thrput_r, thrput);
+		minmax_account(&p->size_r, size);
+		minmax_account(&p->d2c_r, d2c);
+	} else if (BLK_DATADIR(bit_c->action) & BLK_TC_WRITE) {
+		minmax_account(&p->thrput_w, thrput);
+		minmax_account(&p->size_w, size);
+		minmax_account(&p->d2c_w, d2c);
+	} else
+		p->bidir++;
+
+	histlog2_account(p->size_hist, size, &size_hist);
+	histlog2_account(p->d2c_hist, d2c, &d2c_hist);
+	return 0;
+}
+
+static struct trace *blkiomon_alloc_trace(void)
+{
+	struct trace *t = vacant_traces_list;
+	if (t) {
+		vacant_traces_list = t->next;
+		vacant_traces--;
+	} else
+		t = malloc(sizeof(*t));
+	memset(t, 0, sizeof(*t));
+	return t;
+}
+
+static void blkiomon_free_trace(struct trace *t)
+{
+	if (vacant_traces < 256) {
+		t->next = vacant_traces_list;
+		vacant_traces_list = t;
+		vacant_traces++;
+	} else
+		free(t);
+}
+
+static int action(int a)
+{
+	int bits = BLK_TC_WRITE | BLK_TC_READ | BLK_TC_FS | BLK_TC_PC;
+	return a & (BLK_TC_ACT(bits));
+}
+
+static void blkiomon_store_trace(struct trace *t)
+{
+	int i = t->bit.sector % TRACE_HASH_SIZE;
+
+	t->next = thash[i];
+	thash[i] = t;
+}
+
+static struct trace *blkiomon_fetch_trace(struct blk_io_trace *bit)
+{
+	int i = bit->sector % TRACE_HASH_SIZE;
+	struct trace *t, *prev = NULL;
+
+	for (t = thash[i]; t; t = t->next) {
+		if (t->bit.device == bit->device &&
+		    t->bit.sector == bit->sector &&
+		    action(t->bit.action) == action(bit->action)) {
+			if (prev)
+				prev->next = t->next;
+			else
+				thash[i] = t->next;
+			return t;
+		}
+		prev = t;
+	}
+	return NULL;
+}
+
+static struct trace *blkiomon_do_trace(struct trace *t)
+{
+	struct trace *t_stored, *t_old, *t_young;
+
+	/* store trace if there is no match yet */
+	t_stored = blkiomon_fetch_trace(&t->bit);
+	if (!t_stored) {
+		blkiomon_store_trace(t);
+		return blkiomon_alloc_trace();
+	}
+
+	/* figure out older trace and younger trace */
+	if (t_stored->bit.time < t->bit.time) {
+		t_old = t_stored;
+		t_young = t;
+	} else {
+		t_old = t;
+		t_young = t_stored;
+	}
+
+	/* we need an older D trace and a younger C trace */
+	if (t_old->bit.action & BLK_TC_ACT(BLK_TC_ISSUE) &&
+	    t_young->bit.action & BLK_TC_ACT(BLK_TC_COMPLETE)) {
+		/* matching D and C traces - update statistics */
+		match++;
+		blkiomon_account(&t_old->bit, &t_young->bit);
+		blkiomon_free_trace(t_stored);
+		return t;
+	}
+
+	/* no matching D and C traces - keep more recent trace */
+	dump_bits(t_old, t_young, "mismatch");
+	mismatch++;
+	blkiomon_store_trace(t_young);
+	return t_old;
+}
+
+static int blkiomon_dump_drvdata(struct blk_io_trace *bit, void *pdu_buf)
+{
+	if (!drvdata.fn)
+		return 0;
+
+	if (fwrite(bit, sizeof(*bit), 1, drvdata.fp) != 1)
+		goto failed;
+	if (fwrite(pdu_buf, bit->pdu_len, 1, drvdata.fp) != 1)
+		goto failed;
+	if (drvdata.pipe && fflush(drvdata.fp))
+		goto failed;
+	return 0;
+
+failed:
+	fprintf(stderr, "blkiomon: could not write to %s\n", drvdata.fn);
+	fclose(drvdata.fp);
+	drvdata.fn = NULL;
+	return 1;
+}
+
+static int blkiomon_do_fifo(void)
+{
+	struct trace *t;
+	struct blk_io_trace *bit;
+	void *pdu_buf = NULL;
+
+	t = blkiomon_alloc_trace();
+	if (!t)
+		return 1;
+	bit = &t->bit;
+
+	while (up) {
+		if (fread(bit, sizeof(*bit), 1, ifp) != 1) {
+			if (!feof(ifp))
+				fprintf(stderr,
+					"blkiomon: could not read trace");
+			break;
+		}
+		if (ferror(ifp)) {
+			clearerr(ifp);
+			fprintf(stderr, "blkiomon: error while reading trace");
+			break;
+		}
+
+		if (data_is_native == -1 && check_data_endianness(bit->magic)) {
+			fprintf(stderr, "blkiomon: endianess problem\n");
+			break;
+		}
+
+		/* endianess */
+		trace_to_cpu(bit);
+		if (verify_trace(bit)) {
+			fprintf(stderr, "blkiomon: bad trace\n");
+			break;
+		}
+
+		/* read additional trace payload */
+		if (bit->pdu_len) {
+			pdu_buf = realloc(pdu_buf, bit->pdu_len);
+			if (fread(pdu_buf, bit->pdu_len, 1, ifp) != 1) {
+				clearerr(ifp);
+				fprintf(stderr, "blkiomon: could not read payload\n");
+				break;
+			}
+		}
+
+		t->sequence = sequence++;
+
+		/* forward low-level device driver trace to other tool */
+		if (bit->action & BLK_TC_ACT(BLK_TC_DRV_DATA)) {
+			driverdata++;
+			if (blkiomon_dump_drvdata(bit, pdu_buf)) {
+				fprintf(stderr, "blkiomon: could not send trace\n");
+				break;
+			}
+			continue;
+		}
+
+		if (!(bit->action & BLK_TC_ACT(BLK_TC_ISSUE | BLK_TC_COMPLETE)))
+			continue;
+
+		/* try to find matching trace and update statistics */
+		t = blkiomon_do_trace(t);
+		if (!t) {
+			fprintf(stderr, "blkiomon: could not alloc trace\n");
+			break;
+		}
+		bit = &t->bit;
+		/* t and bit will be recycled for next incoming trace */
+	}
+	blkiomon_free_trace(t);
+	free(pdu_buf);
+	return 0;
+}
+
+static int blkiomon_open_output(struct output *out)
+{
+	int mode, vbuf_size;
+
+	if (!out->fn)
+		return 0;
+
+	if (!strcmp(out->fn, "-")) {
+		out->fp = fdopen(STDOUT_FILENO, "w");
+		mode = _IOLBF;
+		vbuf_size = 4096;
+		out->pipe = 1;
+	} else {
+		out->fp = fopen(out->fn, "w");
+		mode = _IOFBF;
+		vbuf_size = 128 * 1024;
+		out->pipe = 0;
+	}
+	if (!out->fp)
+		goto failed;
+	out->buf = malloc(128 * 1024);
+	if (setvbuf(out->fp, out->buf, mode, vbuf_size))
+		goto failed;
+	return 0;
+
+failed:
+	fprintf(stderr, "blkiomon: could not write to %s\n", out->fn);
+	out->fn = NULL;
+	free(out->buf);
+	return 1;
+}
+
+static int blkiomon_open_msg_q(void)
+{
+	key_t key;
+
+	if (!msg_q_name)
+		return 0;
+	if (!msg_q_id || msg_id <= 0)
+		return 1;
+	key = ftok(msg_q_name, msg_q_id);
+	if (key == -1)
+		return 1;
+	while (up) {
+		msg_q = msgget(key, S_IRWXU);
+		if (msg_q >= 0)
+			break;
+	}
+	return (msg_q >= 0 ? 0 : -1);
+}
+
+static void blkiomon_debug(void)
+{
+	int i;
+	struct trace *t;
+
+	if (!debug.fn)
+		return;
+
+	for (i = 0; i < TRACE_HASH_SIZE; i++)
+		for (t = thash[i]; t; t = t->next) {
+			dump_bit(t, "leftover");
+			leftover++;
+		}
+
+	fprintf(debug.fp, "%ld leftover, %ld match, %ld mismatch, "
+		"%ld driverdata, %ld overall\n",
+		leftover, match, mismatch, driverdata, sequence);
+}
+
+#define S_OPTS "b:d:D:h:I:Q:q:m:V"
+
+static char usage_str[] = "\n\nblkiomon " \
+	"-I <interval>       | --interval=<interval>\n" \
+	"[ -h <file>         | --human-readable=<file> ]\n" \
+	"[ -b <file>         | --binary=<file> ]\n" \
+	"[ -d <file>         | --dump-lldd=<file> ]\n" \
+	"[ -D <file>         | --debug=<file> ]\n" \
+	"[ -Q <path name>    | --msg-queue=<path name>]\n" \
+	"[ -q <msg queue id> | --msg-queue-id=<msg queue id>]\n" \
+	"[ -m <msg id>       | --msg-id=<msg id>]\n" \
+	"[ -V                | --version ]\n\n" \
+	"\t-I   Sample interval.\n" \
+	"\t-h   Human-readable output file.\n" \
+	"\t-b   Binary output file.\n" \
+	"\t-d   Output file for data emitted by low level device driver.\n" \
+	"\t-D   Output file for debugging data.\n" \
+	"\t-Qqm Output to message queue using given ID for messages.\n" \
+	"\t-V   Print program version.\n\n";
+
+static struct option l_opts[] = {
+	{
+		.name = "human-readable",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'h'
+	},
+	{
+		.name = "binary",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'b'
+	},
+	{
+		.name = "dump-lldd",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'd'
+	},
+	{
+		.name = "debug",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'D'
+	},
+	{
+		.name = "interval",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'I'
+	},
+	{
+		.name = "msg-queue",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'Q'
+	},
+	{
+		.name = "msg-queue-id",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'q'
+	},
+	{
+		.name = "msg-id",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'm'
+	},
+	{
+		.name = "version",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'V'
+	},
+	{
+		.name = NULL,
+	}
+};
+
+static void blkiomon_signal(int signal)
+{
+	fprintf(stderr, "blkiomon: terminated by signal\n");
+	up = signal & 0;
+}
+
+int main(int argc, char *argv[])
+{
+	int c;
+
+	signal(SIGALRM, blkiomon_signal);
+	signal(SIGINT, blkiomon_signal);
+	signal(SIGTERM, blkiomon_signal);
+	signal(SIGQUIT, blkiomon_signal);
+
+	while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
+		switch (c) {
+		case 'h':
+			human.fn = optarg;
+			break;
+		case 'b':
+			binary.fn = optarg;
+			break;
+		case 'd':
+			drvdata.fn = optarg;
+			break;
+		case 'D':
+			debug.fn = optarg;
+			break;
+		case 'I':
+			interval = atoi(optarg);
+			break;
+		case 'Q':
+			msg_q_name = optarg;
+			break;
+		case 'q':
+			msg_q_id = atoi(optarg);
+			break;
+		case 'm':
+			msg_id = atoi(optarg);
+			break;
+		case 'V':
+			printf("%s version %s\n", argv[0], blkiomon_version);
+			return 0;
+		default:
+			fprintf(stderr, "Usage: %s", usage_str);
+			return 1;
+		}
+	}
+
+	if (interval <= 0) {
+		fprintf(stderr, "Usage: %s", usage_str);
+		return 1;
+	}
+
+	ifp = fdopen(STDIN_FILENO, "r");
+	if (!ifp) {
+		perror("blkiomon: could not open stdin for reading");
+		return 1;
+	}
+
+	if (blkiomon_open_output(&human))
+		return 1;
+	if (blkiomon_open_output(&binary))
+		return 1;
+	if (blkiomon_open_output(&drvdata))
+		return 1;
+	if (blkiomon_open_output(&debug))
+		return 1;
+	if (blkiomon_open_msg_q())
+		return 1;
+
+	if (pthread_create(&interval_thread, NULL, blkiomon_interval, NULL)) {
+		fprintf(stderr, "blkiomon: could not create thread");
+		return 1;
+	}
+
+	blkiomon_do_fifo();
+
+	blkiomon_debug();
+	return 0;
+}
diff --git a/blkiomon.h b/blkiomon.h
new file mode 100644
index 0000000..2ea1716
--- /dev/null
+++ b/blkiomon.h
@@ -0,0 +1,117 @@
+/*
+ * I/O monitor based on block queue trace data
+ *
+ * Copyright IBM Corp. 2008
+ *
+ * Author(s): Martin Peschke <mp3@de.ibm.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ */
+
+#ifndef BLKIOMON_H
+#define BLKIOMON_H
+
+#include <string.h>
+
+#include "stats.h"
+#include "blktrace.h"
+
+#define BLKIOMON_SIZE_BUCKETS 16
+#define BLKIOMON_D2C_BUCKETS 25
+struct blkiomon_stat {
+	__u64 time;
+	__u32 size_hist[BLKIOMON_SIZE_BUCKETS];
+	__u32 d2c_hist[BLKIOMON_D2C_BUCKETS];
+	__u32 device;
+	struct minmax size_r;
+	struct minmax size_w;
+	struct minmax d2c_r;
+	struct minmax d2c_w;
+	struct minmax thrput_r;
+	struct minmax thrput_w;
+	__u64 bidir;
+};
+
+static struct histlog2 size_hist = {
+	.first = 0,
+	.delta = 1024,
+	.num = BLKIOMON_SIZE_BUCKETS
+};
+
+static struct histlog2 d2c_hist = {
+	.first = 0,
+	.delta = 8,
+	.num = BLKIOMON_D2C_BUCKETS
+};
+
+static inline void blkiomon_stat_init(struct blkiomon_stat *bstat)
+{
+	memset(bstat, 0, sizeof(*bstat));
+	minmax_init(&bstat->size_r);
+	minmax_init(&bstat->size_w);
+	minmax_init(&bstat->d2c_r);
+	minmax_init(&bstat->d2c_w);
+	minmax_init(&bstat->thrput_r);
+	minmax_init(&bstat->thrput_w);
+}
+
+static inline void blkiomon_stat_to_be(struct blkiomon_stat *bstat)
+{
+	histlog2_to_be(bstat->size_hist, &size_hist);
+	histlog2_to_be(bstat->d2c_hist, &d2c_hist);
+	minmax_to_be(&bstat->size_r);
+	minmax_to_be(&bstat->size_w);
+	minmax_to_be(&bstat->d2c_r);
+	minmax_to_be(&bstat->d2c_w);
+	minmax_to_be(&bstat->thrput_r);
+	minmax_to_be(&bstat->thrput_w);
+	bstat->bidir = cpu_to_be64(bstat->bidir);
+	bstat->time = cpu_to_be64(bstat->time);
+	bstat->device = cpu_to_be32(bstat->device);
+}
+
+static inline void blkiomon_stat_merge(struct blkiomon_stat *dst,
+				       struct blkiomon_stat *src)
+{
+	histlog2_merge(&size_hist, dst->size_hist, src->size_hist);
+	histlog2_merge(&d2c_hist, dst->d2c_hist, src->d2c_hist);
+	minmax_merge(&dst->size_r, &src->size_r);
+	minmax_merge(&dst->size_w, &src->size_w);
+	minmax_merge(&dst->d2c_r, &src->d2c_r);
+	minmax_merge(&dst->d2c_w, &src->d2c_w);
+	minmax_merge(&dst->thrput_r, &src->thrput_r);
+	minmax_merge(&dst->thrput_w, &src->thrput_w);
+	dst->bidir += src->bidir;
+}
+
+static inline void blkiomon_stat_print(FILE *fp, struct blkiomon_stat *p)
+{
+	if (!fp)
+		return;
+
+	fprintf(fp, "\ntime: %s", ctime((void *)&p->time));
+	fprintf(fp, "device: %d,%d\n", MAJOR(p->device), MINOR(p->device));
+	minmax_print(fp, "sizes read (bytes)", &p->size_r);
+	minmax_print(fp, "sizes write (bytes)", &p->size_w);
+	minmax_print(fp, "d2c read (usec)", &p->d2c_r);
+	minmax_print(fp, "d2c write (usec)", &p->d2c_w);
+	minmax_print(fp, "throughput read (bytes/msec)", &p->thrput_r);
+	minmax_print(fp, "throughput write (bytes/msec)", &p->thrput_w);
+	histlog2_print(fp, "sizes histogram (bytes)", p->size_hist, &size_hist);
+	histlog2_print(fp, "d2c histogram (usec)", p->d2c_hist, &d2c_hist);
+	fprintf(fp, "bidirectional requests: %ld\n", (unsigned long)p->bidir);
+}
+
+#endif
diff --git a/blkparse.c b/blkparse.c
new file mode 100644
index 0000000..a27b3ed
--- /dev/null
+++ b/blkparse.c
@@ -0,0 +1,2959 @@
+/*
+ * block queue tracing parse application
+ *
+ * Copyright (C) 2005 Jens Axboe <axboe@suse.de>
+ * Copyright (C) 2006 Jens Axboe <axboe@kernel.dk>
+ *
+ *  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
+ *
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+#include <locale.h>
+#include <libgen.h>
+
+#include "blktrace.h"
+#include "rbtree.h"
+#include "jhash.h"
+
+static char blkparse_version[] = "1.0.5";
+
+struct skip_info {
+	unsigned long start, end;
+	struct skip_info *prev, *next;
+};
+
+struct per_dev_info {
+	dev_t dev;
+	char *name;
+
+	int backwards;
+	unsigned long long events;
+	unsigned long long first_reported_time;
+	unsigned long long last_reported_time;
+	unsigned long long last_read_time;
+	struct io_stats io_stats;
+	unsigned long skips;
+	unsigned long long seq_skips;
+	unsigned int max_depth[2];
+	unsigned int cur_depth[2];
+
+	struct rb_root rb_track;
+
+	int nfiles;
+	int ncpus;
+
+	unsigned long *cpu_map;
+	unsigned int cpu_map_max;
+
+	struct per_cpu_info *cpus;
+};
+
+/*
+ * some duplicated effort here, we can unify this hash and the ppi hash later
+ */
+struct process_pid_map {
+	pid_t pid;
+	char comm[16];
+	struct process_pid_map *hash_next, *list_next;
+};
+
+#define PPM_HASH_SHIFT	(8)
+#define PPM_HASH_SIZE	(1 << PPM_HASH_SHIFT)
+#define PPM_HASH_MASK	(PPM_HASH_SIZE - 1)
+static struct process_pid_map *ppm_hash_table[PPM_HASH_SIZE];
+
+struct per_process_info {
+	struct process_pid_map *ppm;
+	struct io_stats io_stats;
+	struct per_process_info *hash_next, *list_next;
+	int more_than_one;
+
+	/*
+	 * individual io stats
+	 */
+	unsigned long long longest_allocation_wait[2];
+	unsigned long long longest_dispatch_wait[2];
+	unsigned long long longest_completion_wait[2];
+};
+
+#define PPI_HASH_SHIFT	(8)
+#define PPI_HASH_SIZE	(1 << PPI_HASH_SHIFT)
+#define PPI_HASH_MASK	(PPI_HASH_SIZE - 1)
+static struct per_process_info *ppi_hash_table[PPI_HASH_SIZE];
+static struct per_process_info *ppi_list;
+static int ppi_list_entries;
+
+static struct option l_opts[] = {
+ 	{
+		.name = "act-mask",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'a'
+	},
+	{
+		.name = "set-mask",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'A'
+	},
+	{
+		.name = "batch",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'b'
+	},
+	{
+		.name = "input-directory",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'D'
+	},
+	{
+		.name = "dump-binary",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'd'
+	},
+	{
+		.name = "format",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'f'
+	},
+	{
+		.name = "format-spec",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'F'
+	},
+	{
+		.name = "hash-by-name",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'h'
+	},
+	{
+		.name = "input",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'i'
+	},
+	{
+		.name = "no-msgs",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'M'
+	},
+	{
+		.name = "output",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'o'
+	},
+	{
+		.name = "no-text-output",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'O'
+	},
+	{
+		.name = "quiet",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'q'
+	},
+	{
+		.name = "per-program-stats",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 's'
+	},
+	{
+		.name = "track-ios",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 't'
+	},
+	{
+		.name = "stopwatch",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'w'
+	},
+	{
+		.name = "verbose",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'v'
+	},
+	{
+		.name = "version",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'V'
+	},
+	{
+		.name = NULL,
+	}
+};
+
+/*
+ * for sorting the displayed output
+ */
+struct trace {
+	struct blk_io_trace *bit;
+	struct rb_node rb_node;
+	struct trace *next;
+	unsigned long read_sequence;
+};
+
+static struct rb_root rb_sort_root;
+static unsigned long rb_sort_entries;
+
+static struct trace *trace_list;
+
+/*
+ * allocation cache
+ */
+static struct blk_io_trace *bit_alloc_list;
+static struct trace *t_alloc_list;
+
+/*
+ * for tracking individual ios
+ */
+struct io_track {
+	struct rb_node rb_node;
+
+	struct process_pid_map *ppm;
+	__u64 sector;
+	unsigned long long allocation_time;
+	unsigned long long queue_time;
+	unsigned long long dispatch_time;
+	unsigned long long completion_time;
+};
+
+static int ndevices;
+static struct per_dev_info *devices;
+static char *get_dev_name(struct per_dev_info *, char *, int);
+static int trace_rb_insert_last(struct per_dev_info *, struct trace *);
+
+FILE *ofp = NULL;
+static char *output_name;
+static char *input_dir;
+
+static unsigned long long genesis_time;
+static unsigned long long last_allowed_time;
+static unsigned long long stopwatch_start;	/* start from zero by default */
+static unsigned long long stopwatch_end = -1ULL;	/* "infinity" */
+static unsigned long read_sequence;
+
+static int per_process_stats;
+static int per_device_and_cpu_stats = 1;
+static int track_ios;
+static int ppi_hash_by_pid = 1;
+static int verbose;
+static unsigned int act_mask = -1U;
+static int stats_printed;
+static int bin_output_msgs = 1;
+int data_is_native = -1;
+
+static FILE *dump_fp;
+static char *dump_binary;
+
+static unsigned int t_alloc_cache;
+static unsigned int bit_alloc_cache;
+
+#define RB_BATCH_DEFAULT	(512)
+static unsigned int rb_batch = RB_BATCH_DEFAULT;
+
+static int pipeline;
+static char *pipename;
+
+static int text_output = 1;
+
+#define is_done()	(*(volatile int *)(&done))
+static volatile int done;
+
+struct timespec		abs_start_time;
+static unsigned long long start_timestamp;
+
+static int have_drv_data = 0;
+
+#define JHASH_RANDOM	(0x3af5f2ee)
+
+#define CPUS_PER_LONG	(8 * sizeof(unsigned long))
+#define CPU_IDX(cpu)	((cpu) / CPUS_PER_LONG)
+#define CPU_BIT(cpu)	((cpu) & (CPUS_PER_LONG - 1))
+
+static void output_binary(void *buf, int len)
+{
+	if (dump_binary) {
+		size_t n = fwrite(buf, len, 1, dump_fp);
+		if (n != 1) {
+			perror(dump_binary);
+			fclose(dump_fp);
+			dump_binary = NULL;
+		}
+	}
+}
+
+static void resize_cpu_info(struct per_dev_info *pdi, int cpu)
+{
+	struct per_cpu_info *cpus = pdi->cpus;
+	int ncpus = pdi->ncpus;
+	int new_count = cpu + 1;
+	int new_space, size;
+	char *new_start;
+
+	size = new_count * sizeof(struct per_cpu_info);
+	cpus = realloc(cpus, size);
+	if (!cpus) {
+		char name[20];
+		fprintf(stderr, "Out of memory, CPU info for device %s (%d)\n",
+			get_dev_name(pdi, name, sizeof(name)), size);
+		exit(1);
+	}
+
+	new_start = (char *)cpus + (ncpus * sizeof(struct per_cpu_info));
+	new_space = (new_count - ncpus) * sizeof(struct per_cpu_info);
+	memset(new_start, 0, new_space);
+
+	pdi->ncpus = new_count;
+	pdi->cpus = cpus;
+
+	for (new_count = 0; new_count < pdi->ncpus; new_count++) {
+		struct per_cpu_info *pci = &pdi->cpus[new_count];
+
+		if (!pci->fd) {
+			pci->fd = -1;
+			memset(&pci->rb_last, 0, sizeof(pci->rb_last));
+			pci->rb_last_entries = 0;
+			pci->last_sequence = -1;
+		}
+	}
+}
+
+static struct per_cpu_info *get_cpu_info(struct per_dev_info *pdi, int cpu)
+{
+	struct per_cpu_info *pci;
+
+	if (cpu >= pdi->ncpus)
+		resize_cpu_info(pdi, cpu);
+
+	pci = &pdi->cpus[cpu];
+	pci->cpu = cpu;
+	return pci;
+}
+
+
+static int resize_devices(char *name)
+{
+	int size = (ndevices + 1) * sizeof(struct per_dev_info);
+
+	devices = realloc(devices, size);
+	if (!devices) {
+		fprintf(stderr, "Out of memory, device %s (%d)\n", name, size);
+		return 1;
+	}
+	memset(&devices[ndevices], 0, sizeof(struct per_dev_info));
+	devices[ndevices].name = name;
+	ndevices++;
+	return 0;
+}
+
+static struct per_dev_info *get_dev_info(dev_t dev)
+{
+	struct per_dev_info *pdi;
+	int i;
+
+	for (i = 0; i < ndevices; i++) {
+		if (!devices[i].dev)
+			devices[i].dev = dev;
+		if (devices[i].dev == dev)
+			return &devices[i];
+	}
+
+	if (resize_devices(NULL))
+		return NULL;
+
+	pdi = &devices[ndevices - 1];
+	pdi->dev = dev;
+	pdi->first_reported_time = 0;
+	pdi->last_read_time = 0;
+
+	return pdi;
+}
+
+static void insert_skip(struct per_cpu_info *pci, unsigned long start,
+			unsigned long end)
+{
+	struct skip_info *sip;
+
+	for (sip = pci->skips_tail; sip != NULL; sip = sip->prev) {
+		if (end == (sip->start - 1)) {
+			sip->start = start;
+			return;
+		} else if (start == (sip->end + 1)) {
+			sip->end = end;
+			return;
+		}
+	}
+
+	sip = malloc(sizeof(struct skip_info));
+	sip->start = start;
+	sip->end = end;
+	sip->prev = sip->next = NULL;
+	if (pci->skips_tail == NULL)
+		pci->skips_head = pci->skips_tail = sip;
+	else {
+		sip->prev = pci->skips_tail;
+		pci->skips_tail->next = sip;
+		pci->skips_tail = sip;
+	}
+}
+
+static void remove_sip(struct per_cpu_info *pci, struct skip_info *sip)
+{
+	if (sip->prev == NULL) {
+		if (sip->next == NULL)
+			pci->skips_head = pci->skips_tail = NULL;
+		else {
+			pci->skips_head = sip->next;
+			sip->next->prev = NULL;
+		}
+	} else if (sip->next == NULL) {
+		pci->skips_tail = sip->prev;
+		sip->prev->next = NULL;
+	} else {
+		sip->prev->next = sip->next;
+		sip->next->prev = sip->prev;
+	}
+
+	sip->prev = sip->next = NULL;
+	free(sip);
+}
+
+#define IN_SKIP(sip,seq) (((sip)->start <= (seq)) && ((seq) <= sip->end))
+static int check_current_skips(struct per_cpu_info *pci, unsigned long seq)
+{
+	struct skip_info *sip;
+
+	for (sip = pci->skips_tail; sip != NULL; sip = sip->prev) {
+		if (IN_SKIP(sip, seq)) {
+			if (sip->start == seq) {
+				if (sip->end == seq)
+					remove_sip(pci, sip);
+				else
+					sip->start += 1;
+			} else if (sip->end == seq)
+				sip->end -= 1;
+			else {
+				sip->end = seq - 1;
+				insert_skip(pci, seq + 1, sip->end);
+			}
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static void collect_pdi_skips(struct per_dev_info *pdi)
+{
+	struct skip_info *sip;
+	int cpu;
+
+	pdi->skips = 0;
+	pdi->seq_skips = 0;
+
+	for (cpu = 0; cpu < pdi->ncpus; cpu++) {
+		struct per_cpu_info *pci = &pdi->cpus[cpu];
+
+		for (sip = pci->skips_head; sip != NULL; sip = sip->next) {
+			pdi->skips++;
+			pdi->seq_skips += (sip->end - sip->start + 1);
+			if (verbose)
+				fprintf(stderr,"(%d,%d): skipping %lu -> %lu\n",
+					MAJOR(pdi->dev), MINOR(pdi->dev),
+					sip->start, sip->end);
+		}
+	}
+}
+
+static void cpu_mark_online(struct per_dev_info *pdi, unsigned int cpu)
+{
+	if (cpu >= pdi->cpu_map_max || !pdi->cpu_map) {
+		int new_max = (cpu + CPUS_PER_LONG) & ~(CPUS_PER_LONG - 1);
+		unsigned long *map = malloc(new_max / sizeof(long));
+
+		memset(map, 0, new_max / sizeof(long));
+
+		if (pdi->cpu_map) {
+			memcpy(map, pdi->cpu_map, pdi->cpu_map_max / sizeof(long));
+			free(pdi->cpu_map);
+		}
+
+		pdi->cpu_map = map;
+		pdi->cpu_map_max = new_max;
+	}
+
+	pdi->cpu_map[CPU_IDX(cpu)] |= (1UL << CPU_BIT(cpu));
+}
+
+static inline void cpu_mark_offline(struct per_dev_info *pdi, int cpu)
+{
+	pdi->cpu_map[CPU_IDX(cpu)] &= ~(1UL << CPU_BIT(cpu));
+}
+
+static inline int cpu_is_online(struct per_dev_info *pdi, int cpu)
+{
+	return (pdi->cpu_map[CPU_IDX(cpu)] & (1UL << CPU_BIT(cpu))) != 0;
+}
+
+static inline int ppm_hash_pid(pid_t pid)
+{
+	return jhash_1word(pid, JHASH_RANDOM) & PPM_HASH_MASK;
+}
+
+static struct process_pid_map *find_ppm(pid_t pid)
+{
+	const int hash_idx = ppm_hash_pid(pid);
+	struct process_pid_map *ppm;
+
+	ppm = ppm_hash_table[hash_idx];
+	while (ppm) {
+		if (ppm->pid == pid)
+			return ppm;
+
+		ppm = ppm->hash_next;
+	}
+
+	return NULL;
+}
+
+static struct process_pid_map *add_ppm_hash(pid_t pid, const char *name)
+{
+	const int hash_idx = ppm_hash_pid(pid);
+	struct process_pid_map *ppm;
+
+	ppm = find_ppm(pid);
+	if (!ppm) {
+		ppm = malloc(sizeof(*ppm));
+		memset(ppm, 0, sizeof(*ppm));
+		ppm->pid = pid;
+		memset(ppm->comm, 0, sizeof(ppm->comm));
+		strncpy(ppm->comm, name, sizeof(ppm->comm));
+		ppm->comm[sizeof(ppm->comm) - 1] = '\0';
+		ppm->hash_next = ppm_hash_table[hash_idx];
+		ppm_hash_table[hash_idx] = ppm;
+	}
+
+	return ppm;
+}
+
+static void handle_notify(struct blk_io_trace *bit)
+{
+	void	*payload = (caddr_t) bit + sizeof(*bit);
+	__u32	two32[2];
+
+	switch (bit->action) {
+	case BLK_TN_PROCESS:
+		add_ppm_hash(bit->pid, payload);
+		break;
+
+	case BLK_TN_TIMESTAMP:
+		if (bit->pdu_len != sizeof(two32))
+			return;
+		memcpy(two32, payload, sizeof(two32));
+		if (!data_is_native) {
+			two32[0] = be32_to_cpu(two32[0]);
+			two32[1] = be32_to_cpu(two32[1]);
+		}
+		start_timestamp = bit->time;
+		abs_start_time.tv_sec  = two32[0];
+		abs_start_time.tv_nsec = two32[1];
+		if (abs_start_time.tv_nsec < 0) {
+			abs_start_time.tv_sec--;
+			abs_start_time.tv_nsec += 1000000000;
+		}
+
+		break;
+
+	case BLK_TN_MESSAGE:
+		if (bit->pdu_len > 0) {
+			char msg[bit->pdu_len+1];
+
+			memcpy(msg, (char *)payload, bit->pdu_len);
+			msg[bit->pdu_len] = '\0';
+
+			fprintf(ofp,
+				"%3d,%-3d %2d %8s %5d.%09lu %5u %2s %3s %s\n",
+				MAJOR(bit->device), MINOR(bit->device),
+				bit->cpu, "0", (int) SECONDS(bit->time),
+				(unsigned long) NANO_SECONDS(bit->time),
+				0, "m", "N", msg);
+		}
+		break;
+
+	default:
+		/* Ignore unknown notify events */
+		;
+	}
+}
+
+char *find_process_name(pid_t pid)
+{
+	struct process_pid_map *ppm = find_ppm(pid);
+
+	if (ppm)
+		return ppm->comm;
+
+	return NULL;
+}
+
+static inline int ppi_hash_pid(pid_t pid)
+{
+	return jhash_1word(pid, JHASH_RANDOM) & PPI_HASH_MASK;
+}
+
+static inline int ppi_hash_name(const char *name)
+{
+	return jhash(name, 16, JHASH_RANDOM) & PPI_HASH_MASK;
+}
+
+static inline int ppi_hash(struct per_process_info *ppi)
+{
+	struct process_pid_map *ppm = ppi->ppm;
+
+	if (ppi_hash_by_pid)
+		return ppi_hash_pid(ppm->pid);
+
+	return ppi_hash_name(ppm->comm);
+}
+
+static inline void add_ppi_to_hash(struct per_process_info *ppi)
+{
+	const int hash_idx = ppi_hash(ppi);
+
+	ppi->hash_next = ppi_hash_table[hash_idx];
+	ppi_hash_table[hash_idx] = ppi;
+}
+
+static inline void add_ppi_to_list(struct per_process_info *ppi)
+{
+	ppi->list_next = ppi_list;
+	ppi_list = ppi;
+	ppi_list_entries++;
+}
+
+static struct per_process_info *find_ppi_by_name(char *name)
+{
+	const int hash_idx = ppi_hash_name(name);
+	struct per_process_info *ppi;
+
+	ppi = ppi_hash_table[hash_idx];
+	while (ppi) {
+		struct process_pid_map *ppm = ppi->ppm;
+
+		if (!strcmp(ppm->comm, name))
+			return ppi;
+
+		ppi = ppi->hash_next;
+	}
+
+	return NULL;
+}
+
+static struct per_process_info *find_ppi_by_pid(pid_t pid)
+{
+	const int hash_idx = ppi_hash_pid(pid);
+	struct per_process_info *ppi;
+
+	ppi = ppi_hash_table[hash_idx];
+	while (ppi) {
+		struct process_pid_map *ppm = ppi->ppm;
+
+		if (ppm->pid == pid)
+			return ppi;
+
+		ppi = ppi->hash_next;
+	}
+
+	return NULL;
+}
+
+static struct per_process_info *find_ppi(pid_t pid)
+{
+	struct per_process_info *ppi;
+	char *name;
+
+	if (ppi_hash_by_pid)
+		return find_ppi_by_pid(pid);
+
+	name = find_process_name(pid);
+	if (!name)
+		return NULL;
+
+	ppi = find_ppi_by_name(name);
+	if (ppi && ppi->ppm->pid != pid)
+		ppi->more_than_one = 1;
+
+	return ppi;
+}
+
+/*
+ * struct trace and blktrace allocation cache, we do potentially
+ * millions of mallocs for these structures while only using at most
+ * a few thousand at the time
+ */
+static inline void t_free(struct trace *t)
+{
+	if (t_alloc_cache < 1024) {
+		t->next = t_alloc_list;
+		t_alloc_list = t;
+		t_alloc_cache++;
+	} else
+		free(t);
+}
+
+static inline struct trace *t_alloc(void)
+{
+	struct trace *t = t_alloc_list;
+
+	if (t) {
+		t_alloc_list = t->next;
+		t_alloc_cache--;
+		return t;
+	}
+
+	return malloc(sizeof(*t));
+}
+
+static inline void bit_free(struct blk_io_trace *bit)
+{
+	if (bit_alloc_cache < 1024 && !bit->pdu_len) {
+		/*
+		 * abuse a 64-bit field for a next pointer for the free item
+		 */
+		bit->time = (__u64) (unsigned long) bit_alloc_list;
+		bit_alloc_list = (struct blk_io_trace *) bit;
+		bit_alloc_cache++;
+	} else
+		free(bit);
+}
+
+static inline struct blk_io_trace *bit_alloc(void)
+{
+	struct blk_io_trace *bit = bit_alloc_list;
+
+	if (bit) {
+		bit_alloc_list = (struct blk_io_trace *) (unsigned long) \
+				 bit->time;
+		bit_alloc_cache--;
+		return bit;
+	}
+
+	return malloc(sizeof(*bit));
+}
+
+static inline void __put_trace_last(struct per_dev_info *pdi, struct trace *t)
+{
+	struct per_cpu_info *pci = get_cpu_info(pdi, t->bit->cpu);
+
+	rb_erase(&t->rb_node, &pci->rb_last);
+	pci->rb_last_entries--;
+
+	bit_free(t->bit);
+	t_free(t);
+}
+
+static void put_trace(struct per_dev_info *pdi, struct trace *t)
+{
+	rb_erase(&t->rb_node, &rb_sort_root);
+	rb_sort_entries--;
+
+	trace_rb_insert_last(pdi, t);
+}
+
+static inline int trace_rb_insert(struct trace *t, struct rb_root *root)
+{
+	struct rb_node **p = &root->rb_node;
+	struct rb_node *parent = NULL;
+	struct trace *__t;
+
+	while (*p) {
+		parent = *p;
+
+		__t = rb_entry(parent, struct trace, rb_node);
+
+		if (t->bit->time < __t->bit->time)
+			p = &(*p)->rb_left;
+		else if (t->bit->time > __t->bit->time)
+			p = &(*p)->rb_right;
+		else if (t->bit->device < __t->bit->device)
+			p = &(*p)->rb_left;
+		else if (t->bit->device > __t->bit->device)
+			p = &(*p)->rb_right;
+		else if (t->bit->sequence < __t->bit->sequence)
+			p = &(*p)->rb_left;
+		else	/* >= sequence */
+			p = &(*p)->rb_right;
+	}
+
+	rb_link_node(&t->rb_node, parent, p);
+	rb_insert_color(&t->rb_node, root);
+	return 0;
+}
+
+static inline int trace_rb_insert_sort(struct trace *t)
+{
+	if (!trace_rb_insert(t, &rb_sort_root)) {
+		rb_sort_entries++;
+		return 0;
+	}
+
+	return 1;
+}
+
+static int trace_rb_insert_last(struct per_dev_info *pdi, struct trace *t)
+{
+	struct per_cpu_info *pci = get_cpu_info(pdi, t->bit->cpu);
+
+	if (trace_rb_insert(t, &pci->rb_last))
+		return 1;
+
+	pci->rb_last_entries++;
+
+	if (pci->rb_last_entries > rb_batch * pdi->nfiles) {
+		struct rb_node *n = rb_first(&pci->rb_last);
+
+		t = rb_entry(n, struct trace, rb_node);
+		__put_trace_last(pdi, t);
+	}
+
+	return 0;
+}
+
+static struct trace *trace_rb_find(dev_t device, unsigned long sequence,
+				   struct rb_root *root, int order)
+{
+	struct rb_node *n = root->rb_node;
+	struct rb_node *prev = NULL;
+	struct trace *__t;
+
+	while (n) {
+		__t = rb_entry(n, struct trace, rb_node);
+		prev = n;
+
+		if (device < __t->bit->device)
+			n = n->rb_left;
+		else if (device > __t->bit->device)
+			n = n->rb_right;
+		else if (sequence < __t->bit->sequence)
+			n = n->rb_left;
+		else if (sequence > __t->bit->sequence)
+			n = n->rb_right;
+		else
+			return __t;
+	}
+
+	/*
+	 * hack - the list may not be sequence ordered because some
+	 * events don't have sequence and time matched. so we end up
+	 * being a little off in the rb lookup here, because we don't
+	 * know the time we are looking for. compensate by browsing
+	 * a little ahead from the last entry to find the match
+	 */
+	if (order && prev) {
+		int max = 5;
+
+		while (((n = rb_next(prev)) != NULL) && max--) {
+			__t = rb_entry(n, struct trace, rb_node);
+
+			if (__t->bit->device == device &&
+			    __t->bit->sequence == sequence)
+				return __t;
+
+			prev = n;
+		}
+	}
+
+	return NULL;
+}
+
+static inline struct trace *trace_rb_find_last(struct per_dev_info *pdi,
+					       struct per_cpu_info *pci,
+					       unsigned long seq)
+{
+	return trace_rb_find(pdi->dev, seq, &pci->rb_last, 0);
+}
+
+static inline int track_rb_insert(struct per_dev_info *pdi,struct io_track *iot)
+{
+	struct rb_node **p = &pdi->rb_track.rb_node;
+	struct rb_node *parent = NULL;
+	struct io_track *__iot;
+
+	while (*p) {
+		parent = *p;
+		__iot = rb_entry(parent, struct io_track, rb_node);
+
+		if (iot->sector < __iot->sector)
+			p = &(*p)->rb_left;
+		else if (iot->sector > __iot->sector)
+			p = &(*p)->rb_right;
+		else {
+			fprintf(stderr,
+				"sector alias (%Lu) on device %d,%d!\n",
+				(unsigned long long) iot->sector,
+				MAJOR(pdi->dev), MINOR(pdi->dev));
+			return 1;
+		}
+	}
+
+	rb_link_node(&iot->rb_node, parent, p);
+	rb_insert_color(&iot->rb_node, &pdi->rb_track);
+	return 0;
+}
+
+static struct io_track *__find_track(struct per_dev_info *pdi, __u64 sector)
+{
+	struct rb_node *n = pdi->rb_track.rb_node;
+	struct io_track *__iot;
+
+	while (n) {
+		__iot = rb_entry(n, struct io_track, rb_node);
+
+		if (sector < __iot->sector)
+			n = n->rb_left;
+		else if (sector > __iot->sector)
+			n = n->rb_right;
+		else
+			return __iot;
+	}
+
+	return NULL;
+}
+
+static struct io_track *find_track(struct per_dev_info *pdi, pid_t pid,
+				   __u64 sector)
+{
+	struct io_track *iot;
+
+	iot = __find_track(pdi, sector);
+	if (!iot) {
+		iot = malloc(sizeof(*iot));
+		iot->ppm = find_ppm(pid);
+		if (!iot->ppm)
+			iot->ppm = add_ppm_hash(pid, "unknown");
+		iot->sector = sector;
+		track_rb_insert(pdi, iot);
+	}
+
+	return iot;
+}
+
+static void log_track_frontmerge(struct per_dev_info *pdi,
+				 struct blk_io_trace *t)
+{
+	struct io_track *iot;
+
+	if (!track_ios)
+		return;
+
+	iot = __find_track(pdi, t->sector + t_sec(t));
+	if (!iot) {
+		if (verbose)
+			fprintf(stderr, "merge not found for (%d,%d): %llu\n",
+				MAJOR(pdi->dev), MINOR(pdi->dev),
+				(unsigned long long) t->sector + t_sec(t));
+		return;
+	}
+
+	rb_erase(&iot->rb_node, &pdi->rb_track);
+	iot->sector -= t_sec(t);
+	track_rb_insert(pdi, iot);
+}
+
+static void log_track_getrq(struct per_dev_info *pdi, struct blk_io_trace *t)
+{
+	struct io_track *iot;
+
+	if (!track_ios)
+		return;
+
+	iot = find_track(pdi, t->pid, t->sector);
+	iot->allocation_time = t->time;
+}
+
+static inline int is_remapper(struct per_dev_info *pdi)
+{
+	int major = MAJOR(pdi->dev);
+
+	return (major == 253 || major == 9);
+}
+
+/*
+ * for md/dm setups, the interesting cycle is Q -> C. So track queueing
+ * time here, as dispatch time
+ */
+static void log_track_queue(struct per_dev_info *pdi, struct blk_io_trace *t)
+{
+	struct io_track *iot;
+
+	if (!track_ios)
+		return;
+	if (!is_remapper(pdi))
+		return;
+
+	iot = find_track(pdi, t->pid, t->sector);
+	iot->dispatch_time = t->time;
+}
+
+/*
+ * return time between rq allocation and insertion
+ */
+static unsigned long long log_track_insert(struct per_dev_info *pdi,
+					   struct blk_io_trace *t)
+{
+	unsigned long long elapsed;
+	struct io_track *iot;
+
+	if (!track_ios)
+		return -1;
+
+	iot = find_track(pdi, t->pid, t->sector);
+	iot->queue_time = t->time;
+
+	if (!iot->allocation_time)
+		return -1;
+
+	elapsed = iot->queue_time - iot->allocation_time;
+
+	if (per_process_stats) {
+		struct per_process_info *ppi = find_ppi(iot->ppm->pid);
+		int w = (t->action & BLK_TC_ACT(BLK_TC_WRITE)) != 0;
+
+		if (ppi && elapsed > ppi->longest_allocation_wait[w])
+			ppi->longest_allocation_wait[w] = elapsed;
+	}
+
+	return elapsed;
+}
+
+/*
+ * return time between queue and issue
+ */
+static unsigned long long log_track_issue(struct per_dev_info *pdi,
+					  struct blk_io_trace *t)
+{
+	unsigned long long elapsed;
+	struct io_track *iot;
+
+	if (!track_ios)
+		return -1;
+	if ((t->action & BLK_TC_ACT(BLK_TC_FS)) == 0)
+		return -1;
+
+	iot = __find_track(pdi, t->sector);
+	if (!iot) {
+		if (verbose)
+			fprintf(stderr, "issue not found for (%d,%d): %llu\n",
+				MAJOR(pdi->dev), MINOR(pdi->dev),
+				(unsigned long long) t->sector);
+		return -1;
+	}
+
+	iot->dispatch_time = t->time;
+	elapsed = iot->dispatch_time - iot->queue_time;
+
+	if (per_process_stats) {
+		struct per_process_info *ppi = find_ppi(iot->ppm->pid);
+		int w = (t->action & BLK_TC_ACT(BLK_TC_WRITE)) != 0;
+
+		if (ppi && elapsed > ppi->longest_dispatch_wait[w])
+			ppi->longest_dispatch_wait[w] = elapsed;
+	}
+
+	return elapsed;
+}
+
+/*
+ * return time between dispatch and complete
+ */
+static unsigned long long log_track_complete(struct per_dev_info *pdi,
+					     struct blk_io_trace *t)
+{
+	unsigned long long elapsed;
+	struct io_track *iot;
+
+	if (!track_ios)
+		return -1;
+
+	iot = __find_track(pdi, t->sector);
+	if (!iot) {
+		if (verbose)
+			fprintf(stderr,"complete not found for (%d,%d): %llu\n",
+				MAJOR(pdi->dev), MINOR(pdi->dev),
+				(unsigned long long) t->sector);
+		return -1;
+	}
+
+	iot->completion_time = t->time;
+	elapsed = iot->completion_time - iot->dispatch_time;
+
+	if (per_process_stats) {
+		struct per_process_info *ppi = find_ppi(iot->ppm->pid);
+		int w = (t->action & BLK_TC_ACT(BLK_TC_WRITE)) != 0;
+
+		if (ppi && elapsed > ppi->longest_completion_wait[w])
+			ppi->longest_completion_wait[w] = elapsed;
+	}
+
+	/*
+	 * kill the trace, we don't need it after completion
+	 */
+	rb_erase(&iot->rb_node, &pdi->rb_track);
+	free(iot);
+
+	return elapsed;
+}
+
+
+static struct io_stats *find_process_io_stats(pid_t pid)
+{
+	struct per_process_info *ppi = find_ppi(pid);
+
+	if (!ppi) {
+		ppi = malloc(sizeof(*ppi));
+		memset(ppi, 0, sizeof(*ppi));
+		ppi->ppm = find_ppm(pid);
+		if (!ppi->ppm)
+			ppi->ppm = add_ppm_hash(pid, "unknown");
+		add_ppi_to_hash(ppi);
+		add_ppi_to_list(ppi);
+	}
+
+	return &ppi->io_stats;
+}
+
+static char *get_dev_name(struct per_dev_info *pdi, char *buffer, int size)
+{
+	if (pdi->name)
+		snprintf(buffer, size, "%s", pdi->name);
+	else
+		snprintf(buffer, size, "%d,%d",MAJOR(pdi->dev),MINOR(pdi->dev));
+	return buffer;
+}
+
+static void check_time(struct per_dev_info *pdi, struct blk_io_trace *bit)
+{
+	unsigned long long this = bit->time;
+	unsigned long long last = pdi->last_reported_time;
+
+	pdi->backwards = (this < last) ? 'B' : ' ';
+	pdi->last_reported_time = this;
+}
+
+static inline void __account_m(struct io_stats *ios, struct blk_io_trace *t,
+			       int rw)
+{
+	if (rw) {
+		ios->mwrites++;
+		ios->mwrite_kb += t_kb(t);
+		ios->mwrite_b += t_b(t);
+	} else {
+		ios->mreads++;
+		ios->mread_kb += t_kb(t);
+		ios->mread_b += t_b(t);
+	}
+}
+
+static inline void account_m(struct blk_io_trace *t, struct per_cpu_info *pci,
+			     int rw)
+{
+	__account_m(&pci->io_stats, t, rw);
+
+	if (per_process_stats) {
+		struct io_stats *ios = find_process_io_stats(t->pid);
+
+		__account_m(ios, t, rw);
+	}
+}
+
+static inline void __account_pc_queue(struct io_stats *ios,
+				      struct blk_io_trace *t, int rw)
+{
+	if (rw) {
+		ios->qwrites_pc++;
+		ios->qwrite_kb_pc += t_kb(t);
+		ios->qwrite_b_pc += t_b(t);
+	} else {
+		ios->qreads_pc++;
+		ios->qread_kb += t_kb(t);
+		ios->qread_b_pc += t_b(t);
+	}
+}
+
+static inline void account_pc_queue(struct blk_io_trace *t,
+				    struct per_cpu_info *pci, int rw)
+{
+	__account_pc_queue(&pci->io_stats, t, rw);
+
+	if (per_process_stats) {
+		struct io_stats *ios = find_process_io_stats(t->pid);
+
+		__account_pc_queue(ios, t, rw);
+	}
+}
+
+static inline void __account_pc_issue(struct io_stats *ios, int rw,
+				      unsigned int bytes)
+{
+	if (rw) {
+		ios->iwrites_pc++;
+		ios->iwrite_kb_pc += bytes >> 10;
+		ios->iwrite_b_pc += bytes & 1023;
+	} else {
+		ios->ireads_pc++;
+		ios->iread_kb_pc += bytes >> 10;
+		ios->iread_b_pc += bytes & 1023;
+	}
+}
+
+static inline void account_pc_issue(struct blk_io_trace *t,
+				    struct per_cpu_info *pci, int rw)
+{
+	__account_pc_issue(&pci->io_stats, rw, t->bytes);
+
+	if (per_process_stats) {
+		struct io_stats *ios = find_process_io_stats(t->pid);
+
+		__account_pc_issue(ios, rw, t->bytes);
+	}
+}
+
+static inline void __account_pc_requeue(struct io_stats *ios,
+					struct blk_io_trace *t, int rw)
+{
+	if (rw) {
+		ios->wrqueue_pc++;
+		ios->iwrite_kb_pc -= t_kb(t);
+		ios->iwrite_b_pc -= t_b(t);
+	} else {
+		ios->rrqueue_pc++;
+		ios->iread_kb_pc -= t_kb(t);
+		ios->iread_b_pc -= t_b(t);
+	}
+}
+
+static inline void account_pc_requeue(struct blk_io_trace *t,
+				      struct per_cpu_info *pci, int rw)
+{
+	__account_pc_requeue(&pci->io_stats, t, rw);
+
+	if (per_process_stats) {
+		struct io_stats *ios = find_process_io_stats(t->pid);
+
+		__account_pc_requeue(ios, t, rw);
+	}
+}
+
+static inline void __account_pc_c(struct io_stats *ios, int rw)
+{
+	if (rw)
+		ios->cwrites_pc++;
+	else
+		ios->creads_pc++;
+}
+
+static inline void account_pc_c(struct blk_io_trace *t,
+				struct per_cpu_info *pci, int rw)
+{
+	__account_pc_c(&pci->io_stats, rw);
+
+	if (per_process_stats) {
+		struct io_stats *ios = find_process_io_stats(t->pid);
+
+		__account_pc_c(ios, rw);
+	}
+}
+
+static inline void __account_queue(struct io_stats *ios, struct blk_io_trace *t,
+				   int rw)
+{
+	if (rw) {
+		ios->qwrites++;
+		ios->qwrite_kb += t_kb(t);
+		ios->qwrite_b += t_b(t);
+	} else {
+		ios->qreads++;
+		ios->qread_kb += t_kb(t);
+		ios->qread_b += t_b(t);
+	}
+}
+
+static inline void account_queue(struct blk_io_trace *t,
+				 struct per_cpu_info *pci, int rw)
+{
+	__account_queue(&pci->io_stats, t, rw);
+
+	if (per_process_stats) {
+		struct io_stats *ios = find_process_io_stats(t->pid);
+
+		__account_queue(ios, t, rw);
+	}
+}
+
+static inline void __account_c(struct io_stats *ios, int rw, int bytes)
+{
+	if (rw) {
+		ios->cwrites++;
+		ios->cwrite_kb += bytes >> 10;
+		ios->cwrite_b += bytes & 1023;
+	} else {
+		ios->creads++;
+		ios->cread_kb += bytes >> 10;
+		ios->cread_b += bytes & 1023;
+	}
+}
+
+static inline void account_c(struct blk_io_trace *t, struct per_cpu_info *pci,
+			     int rw, int bytes)
+{
+	__account_c(&pci->io_stats, rw, bytes);
+
+	if (per_process_stats) {
+		struct io_stats *ios = find_process_io_stats(t->pid);
+
+		__account_c(ios, rw, bytes);
+	}
+}
+
+static inline void __account_issue(struct io_stats *ios, int rw,
+				   unsigned int bytes)
+{
+	if (rw) {
+		ios->iwrites++;
+		ios->iwrite_kb += bytes >> 10;
+		ios->iwrite_b  += bytes & 1023;
+	} else {
+		ios->ireads++;
+		ios->iread_kb += bytes >> 10;
+		ios->iread_b  += bytes & 1023;
+	}
+}
+
+static inline void account_issue(struct blk_io_trace *t,
+				 struct per_cpu_info *pci, int rw)
+{
+	__account_issue(&pci->io_stats, rw, t->bytes);
+
+	if (per_process_stats) {
+		struct io_stats *ios = find_process_io_stats(t->pid);
+
+		__account_issue(ios, rw, t->bytes);
+	}
+}
+
+static inline void __account_unplug(struct io_stats *ios, int timer)
+{
+	if (timer)
+		ios->timer_unplugs++;
+	else
+		ios->io_unplugs++;
+}
+
+static inline void account_unplug(struct blk_io_trace *t,
+				  struct per_cpu_info *pci, int timer)
+{
+	__account_unplug(&pci->io_stats, timer);
+
+	if (per_process_stats) {
+		struct io_stats *ios = find_process_io_stats(t->pid);
+
+		__account_unplug(ios, timer);
+	}
+}
+
+static inline void __account_requeue(struct io_stats *ios,
+				     struct blk_io_trace *t, int rw)
+{
+	if (rw) {
+		ios->wrqueue++;
+		ios->iwrite_kb -= t_kb(t);
+		ios->iwrite_b -= t_b(t);
+	} else {
+		ios->rrqueue++;
+		ios->iread_kb -= t_kb(t);
+		ios->iread_b -= t_b(t);
+	}
+}
+
+static inline void account_requeue(struct blk_io_trace *t,
+				   struct per_cpu_info *pci, int rw)
+{
+	__account_requeue(&pci->io_stats, t, rw);
+
+	if (per_process_stats) {
+		struct io_stats *ios = find_process_io_stats(t->pid);
+
+		__account_requeue(ios, t, rw);
+	}
+}
+
+static void log_complete(struct per_dev_info *pdi, struct per_cpu_info *pci,
+			 struct blk_io_trace *t, char *act)
+{
+	process_fmt(act, pci, t, log_track_complete(pdi, t), 0, NULL);
+}
+
+static void log_insert(struct per_dev_info *pdi, struct per_cpu_info *pci,
+		       struct blk_io_trace *t, char *act)
+{
+	process_fmt(act, pci, t, log_track_insert(pdi, t), 0, NULL);
+}
+
+static void log_queue(struct per_cpu_info *pci, struct blk_io_trace *t,
+		      char *act)
+{
+	process_fmt(act, pci, t, -1, 0, NULL);
+}
+
+static void log_issue(struct per_dev_info *pdi, struct per_cpu_info *pci,
+		      struct blk_io_trace *t, char *act)
+{
+	process_fmt(act, pci, t, log_track_issue(pdi, t), 0, NULL);
+}
+
+static void log_merge(struct per_dev_info *pdi, struct per_cpu_info *pci,
+		      struct blk_io_trace *t, char *act)
+{
+	if (act[0] == 'F')
+		log_track_frontmerge(pdi, t);
+
+	process_fmt(act, pci, t, -1ULL, 0, NULL);
+}
+
+static void log_action(struct per_cpu_info *pci, struct blk_io_trace *t,
+			char *act)
+{
+	process_fmt(act, pci, t, -1ULL, 0, NULL);
+}
+
+static void log_generic(struct per_cpu_info *pci, struct blk_io_trace *t,
+			char *act)
+{
+	process_fmt(act, pci, t, -1ULL, 0, NULL);
+}
+
+static void log_unplug(struct per_cpu_info *pci, struct blk_io_trace *t,
+		      char *act)
+{
+	process_fmt(act, pci, t, -1ULL, 0, NULL);
+}
+
+static void log_split(struct per_cpu_info *pci, struct blk_io_trace *t,
+		      char *act)
+{
+	process_fmt(act, pci, t, -1ULL, 0, NULL);
+}
+
+static void log_pc(struct per_cpu_info *pci, struct blk_io_trace *t, char *act)
+{
+	unsigned char *buf = (unsigned char *) t + sizeof(*t);
+
+	process_fmt(act, pci, t, -1ULL, t->pdu_len, buf);
+}
+
+static void dump_trace_pc(struct blk_io_trace *t, struct per_dev_info *pdi,
+			  struct per_cpu_info *pci)
+{
+	int w = (t->action & BLK_TC_ACT(BLK_TC_WRITE)) != 0;
+	int act = t->action & 0xffff;
+
+	switch (act) {
+		case __BLK_TA_QUEUE:
+			log_generic(pci, t, "Q");
+			account_pc_queue(t, pci, w);
+			break;
+		case __BLK_TA_GETRQ:
+			log_generic(pci, t, "G");
+			break;
+		case __BLK_TA_SLEEPRQ:
+			log_generic(pci, t, "S");
+			break;
+		case __BLK_TA_REQUEUE:
+			/*
+			 * can happen if we miss traces, don't let it go
+			 * below zero
+			 */
+			if (pdi->cur_depth[w])
+				pdi->cur_depth[w]--;
+			account_pc_requeue(t, pci, w);
+			log_generic(pci, t, "R");
+			break;
+		case __BLK_TA_ISSUE:
+			account_pc_issue(t, pci, w);
+			pdi->cur_depth[w]++;
+			if (pdi->cur_depth[w] > pdi->max_depth[w])
+				pdi->max_depth[w] = pdi->cur_depth[w];
+			log_pc(pci, t, "D");
+			break;
+		case __BLK_TA_COMPLETE:
+			if (pdi->cur_depth[w])
+				pdi->cur_depth[w]--;
+			log_pc(pci, t, "C");
+			account_pc_c(t, pci, w);
+			break;
+		case __BLK_TA_INSERT:
+			log_pc(pci, t, "I");
+			break;
+		default:
+			fprintf(stderr, "Bad pc action %x\n", act);
+			break;
+	}
+}
+
+static void dump_trace_fs(struct blk_io_trace *t, struct per_dev_info *pdi,
+			  struct per_cpu_info *pci)
+{
+	int w = (t->action & BLK_TC_ACT(BLK_TC_WRITE)) != 0;
+	int act = t->action & 0xffff;
+
+	switch (act) {
+		case __BLK_TA_QUEUE:
+			log_track_queue(pdi, t);
+			account_queue(t, pci, w);
+			log_queue(pci, t, "Q");
+			break;
+		case __BLK_TA_INSERT:
+			log_insert(pdi, pci, t, "I");
+			break;
+		case __BLK_TA_BACKMERGE:
+			account_m(t, pci, w);
+			log_merge(pdi, pci, t, "M");
+			break;
+		case __BLK_TA_FRONTMERGE:
+			account_m(t, pci, w);
+			log_merge(pdi, pci, t, "F");
+			break;
+		case __BLK_TA_GETRQ:
+			log_track_getrq(pdi, t);
+			log_generic(pci, t, "G");
+			break;
+		case __BLK_TA_SLEEPRQ:
+			log_generic(pci, t, "S");
+			break;
+		case __BLK_TA_REQUEUE:
+			/*
+			 * can happen if we miss traces, don't let it go
+			 * below zero
+			 */
+			if (pdi->cur_depth[w])
+				pdi->cur_depth[w]--;
+			account_requeue(t, pci, w);
+			log_queue(pci, t, "R");
+			break;
+		case __BLK_TA_ISSUE:
+			account_issue(t, pci, w);
+			pdi->cur_depth[w]++;
+			if (pdi->cur_depth[w] > pdi->max_depth[w])
+				pdi->max_depth[w] = pdi->cur_depth[w];
+			log_issue(pdi, pci, t, "D");
+			break;
+		case __BLK_TA_COMPLETE:
+			if (pdi->cur_depth[w])
+				pdi->cur_depth[w]--;
+			account_c(t, pci, w, t->bytes);
+			log_complete(pdi, pci, t, "C");
+			break;
+		case __BLK_TA_PLUG:
+			log_action(pci, t, "P");
+			break;
+		case __BLK_TA_UNPLUG_IO:
+			account_unplug(t, pci, 0);
+			log_unplug(pci, t, "U");
+			break;
+		case __BLK_TA_UNPLUG_TIMER:
+			account_unplug(t, pci, 1);
+			log_unplug(pci, t, "UT");
+			break;
+		case __BLK_TA_SPLIT:
+			log_split(pci, t, "X");
+			break;
+		case __BLK_TA_BOUNCE:
+			log_generic(pci, t, "B");
+			break;
+		case __BLK_TA_REMAP:
+			log_generic(pci, t, "A");
+			break;
+		case __BLK_TA_DRV_DATA:
+			have_drv_data = 1;
+			/* dump to binary file only */
+			break;
+		default:
+			fprintf(stderr, "Bad fs action %x\n", t->action);
+			break;
+	}
+}
+
+static void dump_trace(struct blk_io_trace *t, struct per_cpu_info *pci,
+		       struct per_dev_info *pdi)
+{
+	if (text_output) {
+		if (t->action == BLK_TN_MESSAGE)
+			handle_notify(t);
+		else if (t->action & BLK_TC_ACT(BLK_TC_PC))
+			dump_trace_pc(t, pdi, pci);
+		else
+			dump_trace_fs(t, pdi, pci);
+	}
+
+	if (!pdi->events)
+		pdi->first_reported_time = t->time;
+
+	pdi->events++;
+
+	if (bin_output_msgs ||
+			    !(t->action & BLK_TC_ACT(BLK_TC_NOTIFY) &&
+			      t->action == BLK_TN_MESSAGE))
+		output_binary(t, sizeof(*t) + t->pdu_len);
+}
+
+/*
+ * print in a proper way, not too small and not too big. if more than
+ * 1000,000K, turn into M and so on
+ */
+static char *size_cnv(char *dst, unsigned long long num, int in_kb)
+{
+	char suff[] = { '\0', 'K', 'M', 'G', 'P' };
+	unsigned int i = 0;
+
+	if (in_kb)
+		i++;
+
+	while (num > 1000 * 1000ULL && (i < sizeof(suff) - 1)) {
+		i++;
+		num /= 1000;
+	}
+
+	sprintf(dst, "%'8Lu%c", num, suff[i]);
+	return dst;
+}
+
+static void dump_io_stats(struct per_dev_info *pdi, struct io_stats *ios,
+			  char *msg)
+{
+	static char x[256], y[256];
+
+	fprintf(ofp, "%s\n", msg);
+
+	fprintf(ofp, " Reads Queued:    %s, %siB\t",
+			size_cnv(x, ios->qreads, 0),
+			size_cnv(y, ios->qread_kb + (ios->qread_b>>10), 1));
+	fprintf(ofp, " Writes Queued:    %s, %siB\n",
+			size_cnv(x, ios->qwrites, 0),
+			size_cnv(y, ios->qwrite_kb + (ios->qwrite_b>>10), 1));
+	fprintf(ofp, " Read Dispatches: %s, %siB\t",
+			size_cnv(x, ios->ireads, 0),
+			size_cnv(y, ios->iread_kb + (ios->iread_b>>10), 1));
+	fprintf(ofp, " Write Dispatches: %s, %siB\n",
+			size_cnv(x, ios->iwrites, 0),
+			size_cnv(y, ios->iwrite_kb + (ios->iwrite_b>>10), 1));
+	fprintf(ofp, " Reads Requeued:  %s\t\t", size_cnv(x, ios->rrqueue, 0));
+	fprintf(ofp, " Writes Requeued:  %s\n", size_cnv(x, ios->wrqueue, 0));
+	fprintf(ofp, " Reads Completed: %s, %siB\t",
+			size_cnv(x, ios->creads, 0),
+			size_cnv(y, ios->cread_kb + (ios->cread_b>>10), 1));
+	fprintf(ofp, " Writes Completed: %s, %siB\n",
+			size_cnv(x, ios->cwrites, 0),
+			size_cnv(y, ios->cwrite_kb + (ios->cwrite_b>>10), 1));
+	fprintf(ofp, " Read Merges:     %s, %siB\t",
+			size_cnv(x, ios->mreads, 0),
+			size_cnv(y, ios->mread_kb + (ios->mread_b>>10), 1));
+	fprintf(ofp, " Write Merges:     %s, %siB\n",
+			size_cnv(x, ios->mwrites, 0),
+			size_cnv(y, ios->mwrite_kb + (ios->mwrite_b>>10), 1));
+	if (pdi) {
+		fprintf(ofp, " Read depth:      %'8u%8c\t", pdi->max_depth[0], ' ');
+		fprintf(ofp, " Write depth:      %'8u\n", pdi->max_depth[1]);
+	}
+	if (ios->qreads_pc || ios->qwrites_pc || ios->ireads_pc || ios->iwrites_pc ||
+	    ios->rrqueue_pc || ios->wrqueue_pc || ios->creads_pc || ios->cwrites_pc) {
+		fprintf(ofp, " PC Reads Queued: %s, %siB\t",
+			size_cnv(x, ios->qreads_pc, 0),
+			size_cnv(y,
+				ios->qread_kb_pc + (ios->qread_b_pc>>10), 1));
+		fprintf(ofp, " PC Writes Queued: %s, %siB\n",
+			size_cnv(x, ios->qwrites_pc, 0),
+			size_cnv(y,
+				ios->qwrite_kb_pc + (ios->qwrite_b_pc>>10), 1));
+		fprintf(ofp, " PC Read Disp.:   %s, %siB\t",
+			size_cnv(x, ios->ireads_pc, 0),
+			size_cnv(y,
+				ios->iread_kb_pc + (ios->iread_b_pc>>10), 1));
+		fprintf(ofp, " PC Write Disp.:   %s, %siB\n",
+			size_cnv(x, ios->iwrites_pc, 0),
+			size_cnv(y,
+				ios->iwrite_kb_pc + (ios->iwrite_b_pc>>10),
+				1));
+		fprintf(ofp, " PC Reads Req.:   %s\t\t", size_cnv(x, ios->rrqueue_pc, 0));
+		fprintf(ofp, " PC Writes Req.:   %s\n", size_cnv(x, ios->wrqueue_pc, 0));
+		fprintf(ofp, " PC Reads Compl.: %s\t\t", size_cnv(x, ios->creads_pc, 0));
+		fprintf(ofp, " PC Writes Compl.: %s\n", size_cnv(x, ios->cwrites_pc, 0));
+	}
+	fprintf(ofp, " IO unplugs:      %'8lu%8c\t", ios->io_unplugs, ' ');
+	fprintf(ofp, " Timer unplugs:    %'8lu\n", ios->timer_unplugs);
+}
+
+static void dump_wait_stats(struct per_process_info *ppi)
+{
+	unsigned long rawait = ppi->longest_allocation_wait[0] / 1000;
+	unsigned long rdwait = ppi->longest_dispatch_wait[0] / 1000;
+	unsigned long rcwait = ppi->longest_completion_wait[0] / 1000;
+	unsigned long wawait = ppi->longest_allocation_wait[1] / 1000;
+	unsigned long wdwait = ppi->longest_dispatch_wait[1] / 1000;
+	unsigned long wcwait = ppi->longest_completion_wait[1] / 1000;
+
+	fprintf(ofp, " Allocation wait: %'8lu%8c\t", rawait, ' ');
+	fprintf(ofp, " Allocation wait:  %'8lu\n", wawait);
+	fprintf(ofp, " Dispatch wait:   %'8lu%8c\t", rdwait, ' ');
+	fprintf(ofp, " Dispatch wait:    %'8lu\n", wdwait);
+	fprintf(ofp, " Completion wait: %'8lu%8c\t", rcwait, ' ');
+	fprintf(ofp, " Completion wait:  %'8lu\n", wcwait);
+}
+
+static int ppi_name_compare(const void *p1, const void *p2)
+{
+	struct per_process_info *ppi1 = *((struct per_process_info **) p1);
+	struct per_process_info *ppi2 = *((struct per_process_info **) p2);
+	int res;
+
+	res = strverscmp(ppi1->ppm->comm, ppi2->ppm->comm);
+	if (!res)
+		res = ppi1->ppm->pid > ppi2->ppm->pid;
+
+	return res;
+}
+
+static void sort_process_list(void)
+{
+	struct per_process_info **ppis;
+	struct per_process_info *ppi;
+	int i = 0;
+
+	ppis = malloc(ppi_list_entries * sizeof(struct per_process_info *));
+
+	ppi = ppi_list;
+	while (ppi) {
+		ppis[i++] = ppi;
+		ppi = ppi->list_next;
+	}
+
+	qsort(ppis, ppi_list_entries, sizeof(ppi), ppi_name_compare);
+
+	i = ppi_list_entries - 1;
+	ppi_list = NULL;
+	while (i >= 0) {
+		ppi = ppis[i];
+
+		ppi->list_next = ppi_list;
+		ppi_list = ppi;
+		i--;
+	}
+
+	free(ppis);
+}
+
+static void show_process_stats(void)
+{
+	struct per_process_info *ppi;
+
+	sort_process_list();
+
+	ppi = ppi_list;
+	while (ppi) {
+		struct process_pid_map *ppm = ppi->ppm;
+		char name[64];
+
+		if (ppi->more_than_one)
+			sprintf(name, "%s (%u, ...)", ppm->comm, ppm->pid);
+		else
+			sprintf(name, "%s (%u)", ppm->comm, ppm->pid);
+
+		dump_io_stats(NULL, &ppi->io_stats, name);
+		dump_wait_stats(ppi);
+		ppi = ppi->list_next;
+	}
+
+	fprintf(ofp, "\n");
+}
+
+static void show_device_and_cpu_stats(void)
+{
+	struct per_dev_info *pdi;
+	struct per_cpu_info *pci;
+	struct io_stats total, *ios;
+	unsigned long long rrate, wrate, msec;
+	int i, j, pci_events;
+	char line[3 + 8/*cpu*/ + 2 + 32/*dev*/ + 3];
+	char name[32];
+	double ratio;
+
+	for (pdi = devices, i = 0; i < ndevices; i++, pdi++) {
+
+		memset(&total, 0, sizeof(total));
+		pci_events = 0;
+
+		if (i > 0)
+			fprintf(ofp, "\n");
+
+		for (pci = pdi->cpus, j = 0; j < pdi->ncpus; j++, pci++) {
+			if (!pci->nelems)
+				continue;
+
+			ios = &pci->io_stats;
+			total.qreads += ios->qreads;
+			total.qwrites += ios->qwrites;
+			total.creads += ios->creads;
+			total.cwrites += ios->cwrites;
+			total.mreads += ios->mreads;
+			total.mwrites += ios->mwrites;
+			total.ireads += ios->ireads;
+			total.iwrites += ios->iwrites;
+			total.rrqueue += ios->rrqueue;
+			total.wrqueue += ios->wrqueue;
+			total.qread_kb += ios->qread_kb;
+			total.qwrite_kb += ios->qwrite_kb;
+			total.cread_kb += ios->cread_kb;
+			total.cwrite_kb += ios->cwrite_kb;
+			total.iread_kb += ios->iread_kb;
+			total.iwrite_kb += ios->iwrite_kb;
+			total.mread_kb += ios->mread_kb;
+			total.mwrite_kb += ios->mwrite_kb;
+			total.qread_b += ios->qread_b;
+			total.qwrite_b += ios->qwrite_b;
+			total.cread_b += ios->cread_b;
+			total.cwrite_b += ios->cwrite_b;
+			total.iread_b += ios->iread_b;
+			total.iwrite_b += ios->iwrite_b;
+			total.mread_b += ios->mread_b;
+			total.mwrite_b += ios->mwrite_b;
+
+			total.qreads_pc += ios->qreads_pc;
+			total.qwrites_pc += ios->qwrites_pc;
+			total.creads_pc += ios->creads_pc;
+			total.cwrites_pc += ios->cwrites_pc;
+			total.ireads_pc += ios->ireads_pc;
+			total.iwrites_pc += ios->iwrites_pc;
+			total.rrqueue_pc += ios->rrqueue_pc;
+			total.wrqueue_pc += ios->wrqueue_pc;
+			total.qread_kb_pc += ios->qread_kb_pc;
+			total.qwrite_kb_pc += ios->qwrite_kb_pc;
+			total.iread_kb_pc += ios->iread_kb_pc;
+			total.iwrite_kb_pc += ios->iwrite_kb_pc;
+			total.qread_b_pc += ios->qread_b_pc;
+			total.qwrite_b_pc += ios->qwrite_b_pc;
+			total.iread_b_pc += ios->iread_b_pc;
+			total.iwrite_b_pc += ios->iwrite_b_pc;
+
+			total.timer_unplugs += ios->timer_unplugs;
+			total.io_unplugs += ios->io_unplugs;
+
+			snprintf(line, sizeof(line) - 1, "CPU%d (%s):",
+				 j, get_dev_name(pdi, name, sizeof(name)));
+			dump_io_stats(pdi, ios, line);
+			pci_events++;
+		}
+
+		if (pci_events > 1) {
+			fprintf(ofp, "\n");
+			snprintf(line, sizeof(line) - 1, "Total (%s):",
+				 get_dev_name(pdi, name, sizeof(name)));
+			dump_io_stats(NULL, &total, line);
+		}
+
+		wrate = rrate = 0;
+		msec = (pdi->last_reported_time - pdi->first_reported_time) / 1000000;
+		if (msec) {
+			rrate = ((1000 * total.cread_kb) + total.cread_b) /
+									msec;
+			wrate = ((1000 * total.cwrite_kb) + total.cwrite_b) /
+									msec;
+		}
+
+		fprintf(ofp, "\nThroughput (R/W): %'LuKiB/s / %'LuKiB/s\n",
+			rrate, wrate);
+		fprintf(ofp, "Events (%s): %'Lu entries\n",
+			get_dev_name(pdi, line, sizeof(line)), pdi->events);
+
+		collect_pdi_skips(pdi);
+		if (!pdi->skips && !pdi->events)
+			ratio = 0.0;
+		else
+			ratio = 100.0 * ((double)pdi->seq_skips /
+					(double)(pdi->events + pdi->seq_skips));
+		fprintf(ofp, "Skips: %'lu forward (%'llu - %5.1lf%%)\n",
+			pdi->skips, pdi->seq_skips, ratio);
+	}
+}
+
+static void find_genesis(void)
+{
+	struct trace *t = trace_list;
+
+	genesis_time = -1ULL;
+	while (t != NULL) {
+		if (t->bit->time < genesis_time)
+			genesis_time = t->bit->time;
+
+		t = t->next;
+	}
+
+	/* The time stamp record will usually be the first
+	 * record in the trace, but not always.
+	 */
+	if (start_timestamp
+	 && start_timestamp != genesis_time) {
+		long delta = genesis_time - start_timestamp;
+
+		abs_start_time.tv_sec  += SECONDS(delta);
+		abs_start_time.tv_nsec += NANO_SECONDS(delta);
+		if (abs_start_time.tv_nsec < 0) {
+			abs_start_time.tv_nsec += 1000000000;
+			abs_start_time.tv_sec -= 1;
+		} else
+		if (abs_start_time.tv_nsec > 1000000000) {
+			abs_start_time.tv_nsec -= 1000000000;
+			abs_start_time.tv_sec += 1;
+		}
+	}
+}
+
+static inline int check_stopwatch(struct blk_io_trace *bit)
+{
+	if (bit->time < stopwatch_end &&
+	    bit->time >= stopwatch_start)
+		return 0;
+
+	return 1;
+}
+
+/*
+ * return youngest entry read
+ */
+static int sort_entries(unsigned long long *youngest)
+{
+	struct per_dev_info *pdi = NULL;
+	struct per_cpu_info *pci = NULL;
+	struct trace *t;
+
+	if (!genesis_time)
+		find_genesis();
+
+	*youngest = 0;
+	while ((t = trace_list) != NULL) {
+		struct blk_io_trace *bit = t->bit;
+
+		trace_list = t->next;
+
+		bit->time -= genesis_time;
+
+		if (bit->time < *youngest || !*youngest)
+			*youngest = bit->time;
+
+		if (!pdi || pdi->dev != bit->device) {
+			pdi = get_dev_info(bit->device);
+			pci = NULL;
+		}
+
+		if (!pci || pci->cpu != bit->cpu)
+			pci = get_cpu_info(pdi, bit->cpu);
+
+		if (bit->sequence < pci->smallest_seq_read)
+			pci->smallest_seq_read = bit->sequence;
+
+		if (check_stopwatch(bit)) {
+			bit_free(bit);
+			t_free(t);
+			continue;
+		}
+
+		if (trace_rb_insert_sort(t))
+			return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * to continue, we must have traces from all online cpus in the tree
+ */
+static int check_cpu_map(struct per_dev_info *pdi)
+{
+	unsigned long *cpu_map;
+	struct rb_node *n;
+	struct trace *__t;
+	unsigned int i;
+	int ret, cpu;
+
+	/*
+	 * create a map of the cpus we have traces for
+	 */
+	cpu_map = malloc(pdi->cpu_map_max / sizeof(long));
+	memset(cpu_map, 0, sizeof(*cpu_map));
+	n = rb_first(&rb_sort_root);
+	while (n) {
+		__t = rb_entry(n, struct trace, rb_node);
+		cpu = __t->bit->cpu;
+
+		cpu_map[CPU_IDX(cpu)] |= (1UL << CPU_BIT(cpu));
+		n = rb_next(n);
+	}
+
+	/*
+	 * we can't continue if pdi->cpu_map has entries set that we don't
+	 * have in the sort rbtree. the opposite is not a problem, though
+	 */
+	ret = 0;
+	for (i = 0; i < pdi->cpu_map_max / CPUS_PER_LONG; i++) {
+		if (pdi->cpu_map[i] & ~(cpu_map[i])) {
+			ret = 1;
+			break;
+		}
+	}
+
+	free(cpu_map);
+	return ret;
+}
+
+static int check_sequence(struct per_dev_info *pdi, struct trace *t, int force)
+{
+	struct blk_io_trace *bit = t->bit;
+	unsigned long expected_sequence;
+	struct per_cpu_info *pci;
+	struct trace *__t;
+
+	pci = get_cpu_info(pdi, bit->cpu);
+	expected_sequence = pci->last_sequence + 1;
+
+	if (!expected_sequence) {
+		/*
+		 * 1 should be the first entry, just allow it
+		 */
+		if (bit->sequence == 1)
+			return 0;
+		if (bit->sequence == pci->smallest_seq_read)
+			return 0;
+
+		return check_cpu_map(pdi);
+	}
+
+	if (bit->sequence == expected_sequence)
+		return 0;
+
+	/*
+	 * we may not have seen that sequence yet. if we are not doing
+	 * the final run, break and wait for more entries.
+	 */
+	if (expected_sequence < pci->smallest_seq_read) {
+		__t = trace_rb_find_last(pdi, pci, expected_sequence);
+		if (!__t)
+			goto skip;
+
+		__put_trace_last(pdi, __t);
+		return 0;
+	} else if (!force) {
+		return 1;
+	} else {
+skip:
+		if (check_current_skips(pci, bit->sequence))
+			return 0;
+
+		if (expected_sequence < bit->sequence)
+			insert_skip(pci, expected_sequence, bit->sequence - 1);
+		return 0;
+	}
+}
+
+static void show_entries_rb(int force)
+{
+	struct per_dev_info *pdi = NULL;
+	struct per_cpu_info *pci = NULL;
+	struct blk_io_trace *bit;
+	struct rb_node *n;
+	struct trace *t;
+
+	while ((n = rb_first(&rb_sort_root)) != NULL) {
+		if (is_done() && !force && !pipeline)
+			break;
+
+		t = rb_entry(n, struct trace, rb_node);
+		bit = t->bit;
+
+		if (read_sequence - t->read_sequence < 1 && !force)
+			break;
+
+		if (!pdi || pdi->dev != bit->device) {
+			pdi = get_dev_info(bit->device);
+			pci = NULL;
+		}
+
+		if (!pdi) {
+			fprintf(stderr, "Unknown device ID? (%d,%d)\n",
+				MAJOR(bit->device), MINOR(bit->device));
+			break;
+		}
+
+		if (!(bit->action == BLK_TN_MESSAGE) &&
+		    check_sequence(pdi, t, force))
+			break;
+
+		if (!force && bit->time > last_allowed_time)
+			break;
+
+		check_time(pdi, bit);
+
+		if (!pci || pci->cpu != bit->cpu)
+			pci = get_cpu_info(pdi, bit->cpu);
+
+		if (!(bit->action == BLK_TN_MESSAGE))
+			pci->last_sequence = bit->sequence;
+
+		pci->nelems++;
+
+		if (bit->action & (act_mask << BLK_TC_SHIFT))
+			dump_trace(bit, pci, pdi);
+
+		put_trace(pdi, t);
+	}
+}
+
+static int read_data(int fd, void *buffer, int bytes, int block, int *fdblock)
+{
+	int ret, bytes_left, fl;
+	void *p;
+
+	if (block != *fdblock) {
+		fl = fcntl(fd, F_GETFL);
+
+		if (!block) {
+			*fdblock = 0;
+			fcntl(fd, F_SETFL, fl | O_NONBLOCK);
+		} else {
+			*fdblock = 1;
+			fcntl(fd, F_SETFL, fl & ~O_NONBLOCK);
+		}
+	}
+
+	bytes_left = bytes;
+	p = buffer;
+	while (bytes_left > 0) {
+		ret = read(fd, p, bytes_left);
+		if (!ret)
+			return 1;
+		else if (ret < 0) {
+			if (errno != EAGAIN) {
+				perror("read");
+				return -1;
+			}
+
+			/*
+			 * never do partial reads. we can return if we
+			 * didn't read anything and we should not block,
+			 * otherwise wait for data
+			 */
+			if ((bytes_left == bytes) && !block)
+				return 1;
+
+			usleep(10);
+			continue;
+		} else {
+			p += ret;
+			bytes_left -= ret;
+		}
+	}
+
+	return 0;
+}
+
+static inline __u16 get_pdulen(struct blk_io_trace *bit)
+{
+	if (data_is_native)
+		return bit->pdu_len;
+
+	return __bswap_16(bit->pdu_len);
+}
+
+static inline __u32 get_magic(struct blk_io_trace *bit)
+{
+	if (data_is_native)
+		return bit->magic;
+
+	return __bswap_32(bit->magic);
+}
+
+static int read_events(int fd, int always_block, int *fdblock)
+{
+	struct per_dev_info *pdi = NULL;
+	unsigned int events = 0;
+
+	while (!is_done() && events < rb_batch) {
+		struct blk_io_trace *bit;
+		struct trace *t;
+		int pdu_len, should_block, ret;
+		__u32 magic;
+
+		bit = bit_alloc();
+
+		should_block = !events || always_block;
+
+		ret = read_data(fd, bit, sizeof(*bit), should_block, fdblock);
+		if (ret) {
+			bit_free(bit);
+			if (!events && ret < 0)
+				events = ret;
+			break;
+		}
+
+		/*
+		 * look at first trace to check whether we need to convert
+		 * data in the future
+		 */
+		if (data_is_native == -1 && check_data_endianness(bit->magic))
+			break;
+
+		magic = get_magic(bit);
+		if ((magic & 0xffffff00) != BLK_IO_TRACE_MAGIC) {
+			fprintf(stderr, "Bad magic %x\n", magic);
+			break;
+		}
+
+		pdu_len = get_pdulen(bit);
+		if (pdu_len) {
+			void *ptr = realloc(bit, sizeof(*bit) + pdu_len);
+
+			if (read_data(fd, ptr + sizeof(*bit), pdu_len, 1, fdblock)) {
+				bit_free(ptr);
+				break;
+			}
+
+			bit = ptr;
+		}
+
+		trace_to_cpu(bit);
+
+		if (verify_trace(bit)) {
+			bit_free(bit);
+			continue;
+		}
+
+		/*
+		 * not a real trace, so grab and handle it here
+		 */
+		if (bit->action & BLK_TC_ACT(BLK_TC_NOTIFY) && bit->action != BLK_TN_MESSAGE) {
+			handle_notify(bit);
+			output_binary(bit, sizeof(*bit) + bit->pdu_len);
+			continue;
+		}
+
+		t = t_alloc();
+		memset(t, 0, sizeof(*t));
+		t->bit = bit;
+		t->read_sequence = read_sequence;
+
+		t->next = trace_list;
+		trace_list = t;
+
+		if (!pdi || pdi->dev != bit->device)
+			pdi = get_dev_info(bit->device);
+
+		if (bit->time > pdi->last_read_time)
+			pdi->last_read_time = bit->time;
+
+		events++;
+	}
+
+	return events;
+}
+
+/*
+ * Managing input streams
+ */
+
+struct ms_stream {
+	struct ms_stream *next;
+	struct trace *first, *last;
+	struct per_dev_info *pdi;
+	unsigned int cpu;
+};
+
+#define MS_HASH(d, c) ((MAJOR(d) & 0xff) ^ (MINOR(d) & 0xff) ^ (cpu & 0xff))
+
+struct ms_stream *ms_head;
+struct ms_stream *ms_hash[256];
+
+static void ms_sort(struct ms_stream *msp);
+static int ms_prime(struct ms_stream *msp);
+
+static inline struct trace *ms_peek(struct ms_stream *msp)
+{
+	return (msp == NULL) ? NULL : msp->first;
+}
+
+static inline __u64 ms_peek_time(struct ms_stream *msp)
+{
+	return ms_peek(msp)->bit->time;
+}
+
+static inline void ms_resort(struct ms_stream *msp)
+{
+	if (msp->next && ms_peek_time(msp) > ms_peek_time(msp->next)) {
+		ms_head = msp->next;
+		msp->next = NULL;
+		ms_sort(msp);
+	}
+}
+
+static inline void ms_deq(struct ms_stream *msp)
+{
+	msp->first = msp->first->next;
+	if (!msp->first) {
+		msp->last = NULL;
+		if (!ms_prime(msp)) {
+			ms_head = msp->next;
+			msp->next = NULL;
+			return;
+		}
+	}
+
+	ms_resort(msp);
+}
+
+static void ms_sort(struct ms_stream *msp)
+{
+	__u64 msp_t = ms_peek_time(msp);
+	struct ms_stream *this_msp = ms_head;
+
+	if (this_msp == NULL)
+		ms_head = msp;
+	else if (msp_t < ms_peek_time(this_msp)) {
+		msp->next = this_msp;
+		ms_head = msp;
+	}
+	else {
+		while (this_msp->next && ms_peek_time(this_msp->next) < msp_t)
+			this_msp = this_msp->next;
+
+		msp->next = this_msp->next;
+		this_msp->next = msp;
+	}
+}
+
+static int ms_prime(struct ms_stream *msp)
+{
+	__u32 magic;
+	unsigned int i;
+	struct trace *t;
+	struct per_dev_info *pdi = msp->pdi;
+	struct per_cpu_info *pci = get_cpu_info(pdi, msp->cpu);
+	struct blk_io_trace *bit = NULL;
+	int ret, pdu_len, ndone = 0;
+
+	for (i = 0; !is_done() && pci->fd >= 0 && i < rb_batch; i++) {
+		bit = bit_alloc();
+		ret = read_data(pci->fd, bit, sizeof(*bit), 1, &pci->fdblock);
+		if (ret)
+			goto err;
+
+		if (data_is_native == -1 && check_data_endianness(bit->magic))
+			goto err;
+
+		magic = get_magic(bit);
+		if ((magic & 0xffffff00) != BLK_IO_TRACE_MAGIC) {
+			fprintf(stderr, "Bad magic %x\n", magic);
+			goto err;
+
+		}
+
+		pdu_len = get_pdulen(bit);
+		if (pdu_len) {
+			void *ptr = realloc(bit, sizeof(*bit) + pdu_len);
+			ret = read_data(pci->fd, ptr + sizeof(*bit), pdu_len,
+							     1, &pci->fdblock);
+			if (ret) {
+				free(ptr);
+				bit = NULL;
+				goto err;
+			}
+
+			bit = ptr;
+		}
+
+		trace_to_cpu(bit);
+		if (verify_trace(bit))
+			goto err;
+
+		if (bit->cpu != pci->cpu) {
+			fprintf(stderr, "cpu %d trace info has error cpu %d\n",
+				pci->cpu, bit->cpu);
+			continue;
+		}
+
+		if (bit->action & BLK_TC_ACT(BLK_TC_NOTIFY) && bit->action != BLK_TN_MESSAGE) {
+			handle_notify(bit);
+			output_binary(bit, sizeof(*bit) + bit->pdu_len);
+			bit_free(bit);
+
+			i -= 1;
+			continue;
+		}
+
+		if (bit->time > pdi->last_read_time)
+			pdi->last_read_time = bit->time;
+
+		t = t_alloc();
+		memset(t, 0, sizeof(*t));
+		t->bit = bit;
+
+		if (msp->first == NULL)
+			msp->first = msp->last = t;
+		else {
+			msp->last->next = t;
+			msp->last = t;
+		}
+
+		ndone++;
+	}
+
+	return ndone;
+
+err:
+	if (bit) bit_free(bit);
+
+	cpu_mark_offline(pdi, pci->cpu);
+	close(pci->fd);
+	pci->fd = -1;
+
+	return ndone;
+}
+
+static struct ms_stream *ms_alloc(struct per_dev_info *pdi, int cpu)
+{
+	struct ms_stream *msp = malloc(sizeof(*msp));
+
+	msp->next = NULL;
+	msp->first = msp->last = NULL;
+	msp->pdi = pdi;
+	msp->cpu = cpu;
+
+	if (ms_prime(msp))
+		ms_sort(msp);
+
+	return msp;
+}
+
+static int setup_file(struct per_dev_info *pdi, int cpu)
+{
+	int len = 0;
+	struct stat st;
+	char *p, *dname;
+	struct per_cpu_info *pci = get_cpu_info(pdi, cpu);
+
+	pci->cpu = cpu;
+	pci->fdblock = -1;
+
+	p = strdup(pdi->name);
+	dname = dirname(p);
+	if (strcmp(dname, ".")) {
+		input_dir = dname;
+		p = strdup(pdi->name);
+		strcpy(pdi->name, basename(p));
+	}
+	free(p);
+
+	if (input_dir)
+		len = sprintf(pci->fname, "%s/", input_dir);
+
+	snprintf(pci->fname + len, sizeof(pci->fname)-1-len,
+		 "%s.blktrace.%d", pdi->name, pci->cpu);
+	if (stat(pci->fname, &st) < 0)
+		return 0;
+	if (!st.st_size)
+		return 1;
+
+	pci->fd = open(pci->fname, O_RDONLY);
+	if (pci->fd < 0) {
+		perror(pci->fname);
+		return 0;
+	}
+
+	printf("Input file %s added\n", pci->fname);
+	cpu_mark_online(pdi, pci->cpu);
+
+	pdi->nfiles++;
+	ms_alloc(pdi, pci->cpu);
+
+	return 1;
+}
+
+static int handle(struct ms_stream *msp)
+{
+	struct trace *t;
+	struct per_dev_info *pdi;
+	struct per_cpu_info *pci;
+	struct blk_io_trace *bit;
+
+	t = ms_peek(msp);
+
+	bit = t->bit;
+	pdi = msp->pdi;
+	pci = get_cpu_info(pdi, msp->cpu);
+	pci->nelems++;
+	bit->time -= genesis_time;
+
+	if (t->bit->time > stopwatch_end)
+		return 0;
+
+	pdi->last_reported_time = bit->time;
+	if ((bit->action & (act_mask << BLK_TC_SHIFT))&&
+	    t->bit->time >= stopwatch_start)
+		dump_trace(bit, pci, pdi);
+
+	ms_deq(msp);
+
+	if (text_output)
+		trace_rb_insert_last(pdi, t);
+	else {
+		bit_free(t->bit);
+		t_free(t);
+	}
+
+	return 1;
+}
+
+/*
+ * Check if we need to sanitize the name. We allow 'foo', or if foo.blktrace.X
+ * is given, then strip back down to 'foo' to avoid missing files.
+ */
+static int name_fixup(char *name)
+{
+	char *b;
+
+	if (!name)
+		return 1;
+
+	b = strstr(name, ".blktrace.");
+	if (b)
+		*b = '\0';
+
+	return 0;
+}
+
+static int do_file(void)
+{
+	int i, cpu, ret;
+	struct per_dev_info *pdi;
+
+	/*
+	 * first prepare all files for reading
+	 */
+	for (i = 0; i < ndevices; i++) {
+		pdi = &devices[i];
+		ret = name_fixup(pdi->name);
+		if (ret)
+			return ret;
+
+		for (cpu = 0; setup_file(pdi, cpu); cpu++)
+			;
+
+		if (!cpu) {
+			fprintf(stderr,"No input files found for %s\n",
+				pdi->name);
+			return 1;
+		}
+	}
+
+	/*
+	 * Get the initial time stamp
+	 */
+	if (ms_head)
+		genesis_time = ms_peek_time(ms_head);
+
+	/*
+	 * Keep processing traces while any are left
+	 */
+	while (!is_done() && ms_head && handle(ms_head))
+		;
+
+	return 0;
+}
+
+static void do_pipe(int fd)
+{
+	unsigned long long youngest;
+	int events, fdblock;
+
+	last_allowed_time = -1ULL;
+	fdblock = -1;
+	while ((events = read_events(fd, 0, &fdblock)) > 0) {
+		read_sequence++;
+	
+#if 0
+		smallest_seq_read = -1U;
+#endif
+
+		if (sort_entries(&youngest))
+			break;
+
+		if (youngest > stopwatch_end)
+			break;
+
+		show_entries_rb(0);
+	}
+
+	if (rb_sort_entries)
+		show_entries_rb(1);
+}
+
+static int do_fifo(void)
+{
+	int fd;
+
+	if (!strcmp(pipename, "-"))
+		fd = dup(STDIN_FILENO);
+	else
+		fd = open(pipename, O_RDONLY);
+
+	if (fd == -1) {
+		perror("dup stdin");
+		return -1;
+	}
+
+	do_pipe(fd);
+	close(fd);
+	return 0;
+}
+
+static void show_stats(void)
+{
+	if (!ofp)
+		return;
+	if (stats_printed)
+		return;
+
+	stats_printed = 1;
+
+	if (per_process_stats)
+		show_process_stats();
+
+	if (per_device_and_cpu_stats)
+		show_device_and_cpu_stats();
+
+	fflush(ofp);
+}
+
+static void handle_sigint(__attribute__((__unused__)) int sig)
+{
+	done = 1;
+}
+
+/*
+ * Extract start and duration times from a string, allowing
+ * us to specify a time interval of interest within a trace.
+ * Format: "duration" (start is zero) or "start:duration".
+ */
+static int find_stopwatch_interval(char *string)
+{
+	double value;
+	char *sp;
+
+	value = strtod(string, &sp);
+	if (sp == string) {
+		fprintf(stderr,"Invalid stopwatch timer: %s\n", string);
+		return 1;
+	}
+	if (*sp == ':') {
+		stopwatch_start = DOUBLE_TO_NANO_ULL(value);
+		string = sp + 1;
+		value = strtod(string, &sp);
+		if (sp == string || *sp != '\0') {
+			fprintf(stderr,"Invalid stopwatch duration time: %s\n",
+				string);
+			return 1;
+		}
+	} else if (*sp != '\0') {
+		fprintf(stderr,"Invalid stopwatch start timer: %s\n", string);
+		return 1;
+	}
+	stopwatch_end = DOUBLE_TO_NANO_ULL(value);
+	if (stopwatch_end <= stopwatch_start) {
+		fprintf(stderr, "Invalid stopwatch interval: %Lu -> %Lu\n",
+			stopwatch_start, stopwatch_end);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int is_pipe(const char *str)
+{
+	struct stat st;
+
+	if (!strcmp(str, "-"))
+		return 1;
+	if (!stat(str, &st) && S_ISFIFO(st.st_mode))
+		return 1;
+
+	return 0;
+}
+
+#define S_OPTS  "a:A:b:D:d:f:F:hi:o:Oqstw:vVM"
+static char usage_str[] =    "\n\n" \
+	"-i <file>           | --input=<file>\n" \
+	"[ -a <action field> | --act-mask=<action field> ]\n" \
+	"[ -A <action mask>  | --set-mask=<action mask> ]\n" \
+	"[ -b <traces>       | --batch=<traces> ]\n" \
+	"[ -d <file>         | --dump-binary=<file> ]\n" \
+	"[ -D <dir>          | --input-directory=<dir> ]\n" \
+	"[ -f <format>       | --format=<format> ]\n" \
+	"[ -F <spec>         | --format-spec=<spec> ]\n" \
+	"[ -h                | --hash-by-name ]\n" \
+	"[ -o <file>         | --output=<file> ]\n" \
+	"[ -O                | --no-text-output ]\n" \
+	"[ -q                | --quiet ]\n" \
+	"[ -s                | --per-program-stats ]\n" \
+	"[ -t                | --track-ios ]\n" \
+	"[ -w <time>         | --stopwatch=<time> ]\n" \
+	"[ -M                | --no-msgs\n" \
+	"[ -v                | --verbose ]\n" \
+	"[ -V                | --version ]\n\n" \
+	"\t-a Only trace specified actions. See documentation\n" \
+	"\t-A Give trace mask as a single value. See documentation\n" \
+	"\t-b stdin read batching\n" \
+	"\t-d Output file. If specified, binary data is written to file\n" \
+	"\t-D Directory to prepend to input file names\n" \
+	"\t-f Output format. Customize the output format. The format field\n" \
+	"\t   identifies can be found in the documentation\n" \
+	"\t-F Format specification. Can be found in the documentation\n" \
+	"\t-h Hash processes by name, not pid\n" \
+	"\t-i Input file containing trace data, or '-' for stdin\n" \
+	"\t-o Output file. If not given, output is stdout\n" \
+	"\t-O Do NOT output text data\n" \
+	"\t-q Quiet. Don't display any stats at the end of the trace\n" \
+	"\t-s Show per-program io statistics\n" \
+	"\t-t Track individual ios. Will tell you the time a request took\n" \
+	"\t   to get queued, to get dispatched, and to get completed\n" \
+	"\t-w Only parse data between the given time interval in seconds.\n" \
+	"\t   If 'start' isn't given, blkparse defaults the start time to 0\n" \
+	"\t-M Do not output messages to binary file\n" \
+	"\t-v More verbose for marginal errors\n" \
+	"\t-V Print program version info\n\n";
+
+static void usage(char *prog)
+{
+	fprintf(stderr, "Usage: %s %s", prog, usage_str);
+}
+
+int main(int argc, char *argv[])
+{
+	int i, c, ret, mode;
+	int act_mask_tmp = 0;
+	char *ofp_buffer = NULL;
+	char *bin_ofp_buffer = NULL;
+
+	while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
+		switch (c) {
+		case 'a':
+			i = find_mask_map(optarg);
+			if (i < 0) {
+				fprintf(stderr,"Invalid action mask %s\n",
+					optarg);
+				return 1;
+			}
+			act_mask_tmp |= i;
+			break;
+
+		case 'A':
+			if ((sscanf(optarg, "%x", &i) != 1) || 
+							!valid_act_opt(i)) {
+				fprintf(stderr,
+					"Invalid set action mask %s/0x%x\n",
+					optarg, i);
+				return 1;
+			}
+			act_mask_tmp = i;
+			break;
+		case 'i':
+			if (is_pipe(optarg) && !pipeline) {
+				pipeline = 1;
+				pipename = strdup(optarg);
+			} else if (resize_devices(optarg) != 0)
+				return 1;
+			break;
+		case 'D':
+			input_dir = optarg;
+			break;
+		case 'o':
+			output_name = optarg;
+			break;
+		case 'O':
+			text_output = 0;
+			break;
+		case 'b':
+			rb_batch = atoi(optarg);
+			if (rb_batch <= 0)
+				rb_batch = RB_BATCH_DEFAULT;
+			break;
+		case 's':
+			per_process_stats = 1;
+			break;
+		case 't':
+			track_ios = 1;
+			break;
+		case 'q':
+			per_device_and_cpu_stats = 0;
+			break;
+		case 'w':
+			if (find_stopwatch_interval(optarg) != 0)
+				return 1;
+			break;
+		case 'f':
+			set_all_format_specs(optarg);
+			break;
+		case 'F':
+			if (add_format_spec(optarg) != 0)
+				return 1;
+			break;
+		case 'h':
+			ppi_hash_by_pid = 0;
+			break;
+		case 'v':
+			verbose++;
+			break;
+		case 'V':
+			printf("%s version %s\n", argv[0], blkparse_version);
+			return 0;
+		case 'd':
+			dump_binary = optarg;
+			break;
+		case 'M':
+			bin_output_msgs = 0;
+			break;
+		default:
+			usage(argv[0]);
+			return 1;
+		}
+	}
+
+	while (optind < argc) {
+		if (is_pipe(argv[optind]) && !pipeline) {
+			pipeline = 1;
+			pipename = strdup(argv[optind]);
+		} else if (resize_devices(argv[optind]) != 0)
+			return 1;
+		optind++;
+	}
+
+	if (!pipeline && !ndevices) {
+		usage(argv[0]);
+		return 1;
+	}
+
+	if (act_mask_tmp != 0)
+		act_mask = act_mask_tmp;
+
+	memset(&rb_sort_root, 0, sizeof(rb_sort_root));
+
+	signal(SIGINT, handle_sigint);
+	signal(SIGHUP, handle_sigint);
+	signal(SIGTERM, handle_sigint);
+
+	setlocale(LC_NUMERIC, "en_US");
+
+	if (text_output) {
+		if (!output_name) {
+			ofp = fdopen(STDOUT_FILENO, "w");
+			mode = _IOLBF;
+		} else {
+			char ofname[PATH_MAX];
+
+			snprintf(ofname, sizeof(ofname) - 1, "%s", output_name);
+			ofp = fopen(ofname, "w");
+			mode = _IOFBF;
+		}
+
+		if (!ofp) {
+			perror("fopen");
+			return 1;
+		}
+
+		ofp_buffer = malloc(4096);
+		if (setvbuf(ofp, ofp_buffer, mode, 4096)) {
+			perror("setvbuf");
+			return 1;
+		}
+	}
+
+	if (dump_binary) {
+		if (!strcmp(dump_binary, "-"))
+			dump_fp = stdout;
+		else {
+			dump_fp = fopen(dump_binary, "w");
+			if (!dump_fp) {
+				perror(dump_binary);
+				dump_binary = NULL;
+				return 1;
+			}
+		}
+		bin_ofp_buffer = malloc(128 * 1024);
+		if (setvbuf(dump_fp, bin_ofp_buffer, _IOFBF, 128 * 1024)) {
+			perror("setvbuf binary");
+			return 1;
+		}
+	}
+
+	if (pipeline)
+		ret = do_fifo();
+	else
+		ret = do_file();
+
+	if (!ret)
+		show_stats();
+
+	if (have_drv_data && !dump_binary)
+		printf("\ndiscarded traces containing low-level device driver "
+		       "specific data (only available in binary output)\n");
+
+	if (ofp_buffer) {
+		fflush(ofp);
+		free(ofp_buffer);
+	}
+	if (bin_ofp_buffer) {
+		fflush(dump_fp);
+		free(bin_ofp_buffer);
+	}
+	return ret;
+}
diff --git a/blkparse_fmt.c b/blkparse_fmt.c
new file mode 100644
index 0000000..c42e6d7
--- /dev/null
+++ b/blkparse_fmt.c
@@ -0,0 +1,469 @@
+/*
+ * This file contains format parsing code for blkparse, allowing you to
+ * customize the individual action format and generel output format.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "blktrace.h"
+
+#define VALID_SPECS	"ABCDFGIMPQRSTUWX"
+
+#define HEADER		"%D %2c %8s %5T.%9t %5p %2a %3d "
+
+static char *override_format[256];
+
+static inline int valid_spec(int spec)
+{
+	return strchr(VALID_SPECS, spec) != NULL;
+}
+
+void set_all_format_specs(char *option)
+{
+	char *p;
+
+	for (p = VALID_SPECS; *p; p++)
+		if (override_format[(int)(*p)] == NULL)
+			override_format[(int)(*p)] = strdup(option);
+}
+
+int add_format_spec(char *option)
+{
+	int spec = optarg[0];
+
+	if (!valid_spec(spec)) {
+		fprintf(stderr,"Bad format specifier %c\n", spec);
+		return 1;
+	}
+	if (optarg[1] != ',') {
+		fprintf(stderr,"Bad format specifier - need ',' %s\n", option);
+		return 1;
+	}
+	option += 2;
+
+	override_format[spec] = strdup(option);
+
+	return 0;
+}
+
+static inline void fill_rwbs(char *rwbs, struct blk_io_trace *t)
+{
+	int w = t->action & BLK_TC_ACT(BLK_TC_WRITE);
+	int a = t->action & BLK_TC_ACT(BLK_TC_AHEAD);
+	int s = t->action & BLK_TC_ACT(BLK_TC_SYNC);
+	int m = t->action & BLK_TC_ACT(BLK_TC_META);
+	int d = t->action & BLK_TC_ACT(BLK_TC_DISCARD);
+	int f = t->action & BLK_TC_ACT(BLK_TC_FLUSH);
+	int u = t->action & BLK_TC_ACT(BLK_TC_FUA);
+	int i = 0;
+
+	if (f)
+		rwbs[i++] = 'F'; /* flush */
+
+	if (d)
+		rwbs[i++] = 'D';
+	else if (w)
+		rwbs[i++] = 'W';
+	else if (t->bytes)
+		rwbs[i++] = 'R';
+	else
+		rwbs[i++] = 'N';
+
+	if (u)
+		rwbs[i++] = 'F'; /* fua */
+	if (a)
+		rwbs[i++] = 'A';
+	if (s)
+		rwbs[i++] = 'S';
+	if (m)
+		rwbs[i++] = 'M';
+
+	rwbs[i] = '\0';
+}
+
+static const char *
+print_time(unsigned long long timestamp)
+{
+	static char	timebuf[128];
+	struct tm	*tm;
+	time_t		sec;
+	unsigned long	nsec;
+
+	sec  = abs_start_time.tv_sec + SECONDS(timestamp);
+	nsec = abs_start_time.tv_nsec + NANO_SECONDS(timestamp);
+	if (nsec >= 1000000000) {
+		nsec -= 1000000000;
+		sec += 1;
+	}
+
+	tm = localtime(&sec);
+	snprintf(timebuf, sizeof(timebuf),
+			"%02u:%02u:%02u.%06lu",
+			tm->tm_hour,
+			tm->tm_min,
+			tm->tm_sec,
+			nsec / 1000);
+	return timebuf;
+}
+
+static inline int pdu_rest_is_zero(unsigned char *pdu, int len)
+{
+	static char zero[4096];
+
+	return !memcmp(pdu, zero, len);
+}
+
+static char *dump_pdu(unsigned char *pdu_buf, int pdu_len)
+{
+	static char p[4096];
+	int i, len;
+
+	if (!pdu_buf || !pdu_len)
+		return NULL;
+
+	for (len = 0, i = 0; i < pdu_len; i++) {
+		if (i)
+			len += sprintf(p + len, " ");
+
+		len += sprintf(p + len, "%02x", pdu_buf[i]);
+
+		/*
+		 * usually dump for cdb dumps where we can see lots of
+		 * zeroes, stop when the rest is just zeroes and indicate
+		 * so with a .. appended
+		 */
+		if (!pdu_buf[i] && pdu_rest_is_zero(pdu_buf + i, pdu_len - i)) {
+			sprintf(p + len, " ..");
+			break;
+		}
+	}
+
+	return p;
+}
+
+#define pdu_start(t)	(((void *) (t) + sizeof(struct blk_io_trace)))
+
+static unsigned int get_pdu_int(struct blk_io_trace *t)
+{
+	__u64 *val = pdu_start(t);
+
+	return be64_to_cpu(*val);
+}
+
+static void get_pdu_remap(struct blk_io_trace *t, struct blk_io_trace_remap *r)
+{
+	struct blk_io_trace_remap *__r = pdu_start(t);
+	__u64 sector_from = __r->sector_from;
+
+	r->device_from = be32_to_cpu(__r->device_from);
+	r->device_to   = be32_to_cpu(__r->device_to);
+	r->sector_from = be64_to_cpu(sector_from);
+}
+
+static void print_field(char *act, struct per_cpu_info *pci,
+			struct blk_io_trace *t, unsigned long long elapsed,
+			int pdu_len, unsigned char *pdu_buf, char field,
+			int minus, int has_w, int width)
+{
+	char format[64];
+
+	if (has_w) {
+		if (minus)
+			sprintf(format, "%%-%d", width);
+		else
+			sprintf(format, "%%%d", width);
+	} else
+		sprintf(format, "%%");
+
+	switch (field) {
+	case 'a':
+		fprintf(ofp, strcat(format, "s"), act);
+		break;
+	case 'c':
+		fprintf(ofp, strcat(format, "d"), pci->cpu);
+		break;
+	case 'C': {
+		char *name = find_process_name(t->pid);
+
+		fprintf(ofp, strcat(format, "s"), name);
+		break;
+	}
+	case 'd': {
+		char rwbs[8];
+
+		fill_rwbs(rwbs, t);
+		fprintf(ofp, strcat(format, "s"), rwbs);
+		break;
+	}
+	case 'D':	/* format width ignored */
+		fprintf(ofp,"%3d,%-3d", MAJOR(t->device), MINOR(t->device));
+		break;
+	case 'e':
+		fprintf(ofp, strcat(format, "d"), t->error);
+		break;
+	case 'M':
+		fprintf(ofp, strcat(format, "d"), MAJOR(t->device));
+		break;
+	case 'm':
+		fprintf(ofp, strcat(format, "d"), MINOR(t->device));
+		break;
+	case 'n':
+		fprintf(ofp, strcat(format, "u"), t_sec(t));
+		break;
+	case 'N':
+		fprintf(ofp, strcat(format, "u"), t->bytes);
+		break;
+	case 'p':
+		fprintf(ofp, strcat(format, "u"), t->pid);
+		break;
+	case 'P': { /* format width ignored */
+		char *p = dump_pdu(pdu_buf, pdu_len);
+		if (p)
+			fprintf(ofp, "%s", p);
+		break;
+	}
+	case 's':
+		fprintf(ofp, strcat(format, "ld"), t->sequence);
+		break;
+	case 'S':
+		fprintf(ofp, strcat(format, "lu"), t->sector);
+		break;
+	case 't':
+		sprintf(format, "%%0%dlu", has_w ? width : 9);
+		fprintf(ofp, format, NANO_SECONDS(t->time));
+		break;
+	case 'T':
+		fprintf(ofp, strcat(format, "d"), SECONDS(t->time));
+		break;
+	case 'u':
+		if (elapsed == -1ULL) {
+			fprintf(stderr, "Expecting elapsed value\n");
+			exit(1);
+		}
+		fprintf(ofp, strcat(format, "llu"), elapsed / 1000);
+		break;
+	case 'U':
+		fprintf(ofp, strcat(format, "u"), get_pdu_int(t));
+		break;
+	case 'z':
+		fprintf(ofp, strcat(format, "s"), print_time(t->time));
+		break;
+	default:
+		fprintf(ofp,strcat(format, "c"), field);
+		break;
+	}
+}
+
+static char *parse_field(char *act, struct per_cpu_info *pci,
+			 struct blk_io_trace *t, unsigned long long elapsed,
+			 int pdu_len, unsigned char *pdu_buf,
+			 char *master_format)
+{
+	int minus = 0;
+	int has_w = 0;
+	int width = 0;
+	char *p = master_format;
+
+	if (*p == '-') {
+		minus = 1;
+		p++;
+	}
+	if (isdigit(*p)) {
+		has_w = 1;
+		do {
+			width = (width * 10) + (*p++ - '0');
+		} while ((*p) && (isdigit(*p)));
+	}
+	if (*p) {
+		print_field(act, pci, t, elapsed, pdu_len, pdu_buf, *p++,
+			    minus, has_w, width);
+	}
+	return p;
+}
+
+static void process_default(char *act, struct per_cpu_info *pci,
+			    struct blk_io_trace *t, unsigned long long elapsed,
+			    int pdu_len, unsigned char *pdu_buf)
+{
+	struct blk_io_trace_remap r = { .device_from = 0, };
+	char rwbs[8];
+	char *name;
+
+	fill_rwbs(rwbs, t);
+
+	 /*
+	  * For remaps we have to modify the device using the remap structure
+	  * passed up.
+	  */
+	 if (act[0] == 'A') {
+		 get_pdu_remap(t, &r);
+		 t->device = r.device_to;
+	 }
+
+	/*
+	 * The header is always the same
+	 */
+	fprintf(ofp, "%3d,%-3d %2d %8d %5d.%09lu %5u %2s %3s ",
+		MAJOR(t->device), MINOR(t->device), pci->cpu, t->sequence,
+		(int) SECONDS(t->time), (unsigned long) NANO_SECONDS(t->time),
+		t->pid, act, rwbs);
+
+	name = find_process_name(t->pid);
+
+	switch (act[0]) {
+	case 'R':	/* Requeue */
+	case 'C': 	/* Complete */
+		if (t->action & BLK_TC_ACT(BLK_TC_PC)) {
+			char *p = dump_pdu(pdu_buf, pdu_len);
+			if (p)
+				fprintf(ofp, "(%s) ", p);
+			fprintf(ofp, "[%d]\n", t->error);
+		} else {
+			if (elapsed != -1ULL) {
+				if (t_sec(t))
+					fprintf(ofp, "%llu + %u (%8llu) [%d]\n",
+						(unsigned long long) t->sector,
+						t_sec(t), elapsed, t->error);
+				else
+					fprintf(ofp, "%llu (%8llu) [%d]\n",
+						(unsigned long long) t->sector,
+						elapsed, t->error);
+			} else {
+				if (t_sec(t))
+					fprintf(ofp, "%llu + %u [%d]\n",
+						(unsigned long long) t->sector,
+						t_sec(t), t->error);
+				else
+					fprintf(ofp, "%llu [%d]\n",
+						(unsigned long long) t->sector,
+						t->error);
+			}
+		}
+		break;
+
+	case 'D': 	/* Issue */
+	case 'I': 	/* Insert */
+	case 'Q': 	/* Queue */
+	case 'B':	/* Bounce */
+		if (t->action & BLK_TC_ACT(BLK_TC_PC)) {
+			char *p;
+			fprintf(ofp, "%u ", t->bytes);
+			p = dump_pdu(pdu_buf, pdu_len);
+			if (p)
+				fprintf(ofp, "(%s) ", p);
+			fprintf(ofp, "[%s]\n", name);
+		} else {
+			if (elapsed != -1ULL) {
+				if (t_sec(t))
+					fprintf(ofp, "%llu + %u (%8llu) [%s]\n",
+						(unsigned long long) t->sector,
+						t_sec(t), elapsed, name);
+				else
+					fprintf(ofp, "(%8llu) [%s]\n", elapsed,
+						name);
+			} else {
+				if (t_sec(t))
+					fprintf(ofp, "%llu + %u [%s]\n",
+						(unsigned long long) t->sector,
+						t_sec(t), name);
+				else
+					fprintf(ofp, "[%s]\n", name);
+			}
+		}
+		break;
+
+	case 'M':	/* Back merge */
+	case 'F':	/* Front merge */
+	case 'G':	/* Get request */
+	case 'S':	/* Sleep request */
+		if (t_sec(t))
+			fprintf(ofp, "%llu + %u [%s]\n",
+				(unsigned long long) t->sector, t_sec(t), name);
+		else
+			fprintf(ofp, "[%s]\n", name);
+		break;
+
+	case 'P':	/* Plug */
+		fprintf(ofp, "[%s]\n", name);
+		break;
+
+	case 'U':	/* Unplug IO */
+	case 'T': 	/* Unplug timer */
+		fprintf(ofp, "[%s] %u\n", name, get_pdu_int(t));
+		break;
+
+	case 'A': 	/* remap */
+		get_pdu_remap(t, &r);
+		fprintf(ofp, "%llu + %u <- (%d,%d) %llu\n",
+			(unsigned long long) t->sector, t_sec(t),
+			MAJOR(r.device_from), MINOR(r.device_from),
+			(unsigned long long) r.sector_from);
+		break;
+
+	case 'X': 	/* Split */
+		fprintf(ofp, "%llu / %u [%s]\n", (unsigned long long) t->sector,
+			get_pdu_int(t), name);
+		break;
+
+	case 'm':	/* Message */
+		fprintf(ofp, "%*s\n", pdu_len, pdu_buf);
+		break;
+
+	default:
+		fprintf(stderr, "Unknown action %c\n", act[0]);
+		break;
+	}
+
+}
+
+void process_fmt(char *act, struct per_cpu_info *pci, struct blk_io_trace *t,
+		 unsigned long long elapsed, int pdu_len,
+		 unsigned char *pdu_buf)
+{
+	char *p = override_format[(int) *act];
+
+	if (!p) {
+		process_default(act, pci, t, elapsed, pdu_len, pdu_buf);
+		return;
+	}
+
+	while (*p) {
+		switch (*p) {
+		case '%': 	/* Field specifier */
+			p++;
+			if (*p == '%')
+				fprintf(ofp, "%c", *p++);
+			else if (!*p)
+				fprintf(ofp, "%c", '%');
+			else
+				p = parse_field(act, pci, t, elapsed,
+						pdu_len, pdu_buf, p);
+			break;
+		case '\\': {	/* escape */
+			switch (p[1]) {
+			case 'b': fprintf(ofp, "\b"); break;
+			case 'n': fprintf(ofp, "\n"); break;
+			case 'r': fprintf(ofp, "\r"); break;
+			case 't': fprintf(ofp, "\t"); break;
+			default:
+				fprintf(stderr,
+					"Invalid escape char in format %c\n",
+					p[1]);
+				exit(1);
+				/*NOTREACHED*/
+			}
+			p += 2;
+			break;
+		}
+		default:
+			fprintf(ofp, "%c", *p++);
+			break;
+		}
+	}
+}
+
+
diff --git a/blkrawverify.c b/blkrawverify.c
new file mode 100644
index 0000000..ed5d258
--- /dev/null
+++ b/blkrawverify.c
@@ -0,0 +1,324 @@
+/*
+ * block queue tracing application
+ *
+ * Copyright (C) 2006 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "blktrace.h"
+
+struct trace_info {
+	int bit_field;
+	char *string;
+};
+
+int data_is_native = -1;
+
+#define TRACE_TO_STRING(f)	{.bit_field = f, .string = #f}
+static struct trace_info traces[] = {
+	TRACE_TO_STRING( BLK_TC_READ ),
+	TRACE_TO_STRING( BLK_TC_WRITE ),
+	TRACE_TO_STRING( BLK_TC_FLUSH ),
+	TRACE_TO_STRING( BLK_TC_SYNC ),
+	TRACE_TO_STRING( BLK_TC_QUEUE ),
+	TRACE_TO_STRING( BLK_TC_REQUEUE ),
+	TRACE_TO_STRING( BLK_TC_ISSUE ),
+	TRACE_TO_STRING( BLK_TC_COMPLETE ),
+	TRACE_TO_STRING( BLK_TC_FS ),
+	TRACE_TO_STRING( BLK_TC_PC ),
+	TRACE_TO_STRING( BLK_TC_AHEAD ),
+	TRACE_TO_STRING( BLK_TC_META ),
+	TRACE_TO_STRING( BLK_TC_DISCARD ),
+	TRACE_TO_STRING( BLK_TC_FUA ),
+};
+#define N_TRACES (sizeof(traces) / sizeof(struct trace_info))
+
+struct act_info {
+	__u32 val;
+	char *string;
+};
+
+#define ACT_TO_STRING(f)	{.val = f, .string = #f}
+static struct act_info acts[] = {
+	ACT_TO_STRING( __BLK_TA_QUEUE ),
+	ACT_TO_STRING( __BLK_TA_QUEUE ),
+	ACT_TO_STRING( __BLK_TA_BACKMERGE ),
+	ACT_TO_STRING( __BLK_TA_FRONTMERGE ),
+	ACT_TO_STRING( __BLK_TA_GETRQ ),
+	ACT_TO_STRING( __BLK_TA_SLEEPRQ ),
+	ACT_TO_STRING( __BLK_TA_REQUEUE ),
+	ACT_TO_STRING( __BLK_TA_ISSUE ),
+	ACT_TO_STRING( __BLK_TA_COMPLETE ),
+	ACT_TO_STRING( __BLK_TA_PLUG ),
+	ACT_TO_STRING( __BLK_TA_UNPLUG_IO ),
+	ACT_TO_STRING( __BLK_TA_UNPLUG_TIMER ),
+	ACT_TO_STRING( __BLK_TA_INSERT ),
+	ACT_TO_STRING( __BLK_TA_SPLIT ),
+	ACT_TO_STRING( __BLK_TA_BOUNCE ),
+	ACT_TO_STRING( __BLK_TA_REMAP )
+};
+#define N_ACTS (sizeof(acts) / sizeof(struct act_info))
+
+static char *act_to_str(__u32 action)
+{
+	static char buf[1024];
+	unsigned int i;
+	unsigned int act = action & 0xffff;
+	unsigned int trace = (action >> BLK_TC_SHIFT) & 0xffff;
+
+	if (act < N_ACTS) {
+		sprintf(buf, "%s ", acts[act].string);
+		for (i = 0; i < N_TRACES; i++)
+			if (trace & (1 << i)) {
+				char buf2[1024];
+				sprintf(buf2, "| %s ", traces[i].string);
+				strcat(buf, buf2);
+			}
+	}
+	else
+		sprintf(buf, "Invalid action=%08x", action);
+
+	return buf;
+}
+
+static void dump_trace(FILE *ofp, char *prefix, struct blk_io_trace *bit)
+{
+	fprintf(ofp, "    Dump %s\n", prefix);
+	fprintf(ofp, "        %8s: %08x\n", "magic", bit->magic);
+	fprintf(ofp, "        %8s: %u\n", "sequence", bit->sequence);
+	fprintf(ofp, "        %8s: %llu\n", "time", (unsigned long long) bit->time);
+	fprintf(ofp, "        %8s: %llu\n", "sector", (unsigned long long) bit->sector);
+	fprintf(ofp, "        %8s: %u\n", "bytes", bit->bytes);
+	fprintf(ofp, "        %8s: %s\n", "action", act_to_str(bit->action));
+	fprintf(ofp, "        %8s: %u\n", "bytes", bit->bytes);
+	fprintf(ofp, "        %8s: %u\n", "cpu", bit->cpu);
+	fprintf(ofp, "        %8s: %u\n", "error", bit->error);
+	fprintf(ofp, "        %8s: %u\n", "pdu_len", bit->pdu_len);
+	fprintf(ofp, "        %8s: (%u,%u)\n\n", "device", MAJOR(bit->device),
+						           MINOR(bit->device));
+}
+
+static int process(FILE **fp, char *devname, char *file, unsigned int cpu)
+{
+#	define SWAP_BITS() do {						\
+		if (bit_save) {						\
+			struct blk_io_trace *tmp = bit_save;		\
+			bit_save = bit;					\
+			bit = tmp;					\
+		}							\
+		else {							\
+			bit_save = bit;					\
+			bit = malloc(sizeof(struct blk_io_trace));	\
+		}							\
+	} while (0)
+
+#	define INC_BAD(str) do {					\
+		nbad++;							\
+		fprintf(ofp, "    ----------------\n");			\
+		if (bit_save) dump_trace(ofp,"seq-1",bit_save);		\
+		dump_trace(ofp, str, bit);				\
+		SWAP_BITS();						\
+	} while (0)
+
+	size_t n;
+	FILE *ifp, *ofp;
+	__u32 save_device = 0, save_sequence = 0;
+	__u64 save_time = 0;
+	struct blk_io_trace *bit_save = NULL;
+	struct blk_io_trace *bit = malloc(sizeof(struct blk_io_trace));
+	unsigned int ngood = 0;
+	unsigned int nbad = 0;
+	unsigned int nbad_trace = 0, nbad_pdu = 0, nbad_cpu = 0;
+	unsigned int nbad_seq = 0, nbad_dev = 0, nbad_time = 0;
+	char ofname[1024];
+
+	ifp = fopen(file, "r");
+	if (!ifp)
+		return 0;
+
+	sprintf(ofname, "%s.verify.out", devname);
+
+	if (!*fp) {
+		*fp = fopen(ofname, "w");
+		if (*fp == NULL) {
+			fprintf(stderr,"Failed to open %s (%s), skipping\n",
+				ofname, strerror(errno));
+			fclose(ifp);
+			return 0;
+		}
+		fprintf(*fp, "\n---------------\n" );
+		fprintf(*fp, "Verifying %s\n", devname);
+	}
+
+	ofp = *fp;
+	while ((n = fread(bit, sizeof(struct blk_io_trace), 1, ifp)) == 1) {
+		if (ferror(ifp)) {
+			clearerr(ifp);
+			perror("fread");
+			break;
+		}
+		if (data_is_native == -1)
+			check_data_endianness(bit->magic);
+
+		trace_to_cpu(bit);
+
+		if (!CHECK_MAGIC(bit)) {
+			INC_BAD("bad trace");
+			continue;
+		}
+
+		if ((bit->magic & 0xff) != SUPPORTED_VERSION) {
+			fprintf(stderr, "unsupported trace version\n");
+			break;
+		}
+
+		if (bit->pdu_len) {
+			char *pdu_buf;
+
+			pdu_buf = malloc(bit->pdu_len);
+			n = fread(pdu_buf, bit->pdu_len, 1, ifp);
+			if (n == 0) {
+				INC_BAD("bad pdu");
+				nbad_seq++;
+				free(pdu_buf);
+				break;
+			}
+			free(pdu_buf);
+		}
+
+		if (bit->cpu != cpu) {
+			INC_BAD("bad cpu");
+			nbad_cpu++;
+			continue;
+		}
+
+		/*
+		 * skip notify traces, they don't have valid sequences
+		 */
+		if (bit->action & BLK_TC_ACT(BLK_TC_NOTIFY))
+			continue;
+
+		if (ngood) {
+			if (bit->sequence <= save_sequence) {
+				INC_BAD("bad seq");
+				nbad_seq++;
+				continue;
+			}
+			else if (bit->time <= save_time) {
+				INC_BAD("time regression");
+				nbad_time++;
+				continue;
+			}
+			else if (bit->device != save_device) {
+				INC_BAD("bad dev");
+				nbad_dev++;
+				continue;
+			}
+		}
+
+		save_sequence = bit->sequence;
+		save_time = bit->time;
+		save_device = bit->device;
+
+		ngood++;
+		SWAP_BITS();
+	}
+
+	if (n == 0 && !feof(ifp))
+		fprintf(stderr,"%s: fread failed %d/%s\n",
+		        file, errno, strerror(errno));
+	fclose(ifp);
+
+	fprintf(ofp, "    ---------------------\n");
+	fprintf(ofp, "    Summary for cpu %d:\n", cpu);
+	fprintf(ofp, "    %10d valid + %10d invalid (%5.1f%%) processed\n\n",
+		ngood, nbad,
+		ngood ? 100.0 * (float)ngood / (float)(ngood + nbad) : 0.0);
+
+	if (nbad) {
+		if (nbad_trace)
+			fprintf(ofp, "%8s %d traces\n", "", nbad_trace);
+		if (nbad_trace)
+			fprintf(ofp, "%8s %d pdu\n", "", nbad_pdu);
+		if (nbad_cpu)
+			fprintf(ofp, "%8s %d cpu\n", "", nbad_cpu);
+		if (nbad_seq)
+			fprintf(ofp, "%8s %d seq\n", "", nbad_seq);
+		if (nbad_dev)
+			fprintf(ofp, "%8s %d dev\n", "", nbad_dev);
+		if (nbad_time)
+			fprintf(ofp, "%8s %d time\n", "", nbad_time);
+		fprintf(ofp,"\n");
+	}
+
+	return nbad;
+}
+
+int main(int argc, char *argv[])
+{
+	char *devname;
+	struct stat st;
+	int i, cpu, nbad, rval = 0;
+	FILE *ofp;
+	char *ofname = malloc(1024);
+	char *fname = malloc(1024);
+
+	if (argc < 2) {
+		fprintf(stderr,"FATAL: Need device name(s)\n");
+		fprintf(stderr,"Usage: blkrawverify <dev> [<dev>...]\n");
+		exit(1);
+	}
+
+	for (i = 1; i < argc; i++) {
+		devname = argv[i];
+		sprintf(ofname, "%s.verify.out", devname);
+		ofp = NULL;
+
+		printf("Verifying %s\n", devname); fflush(stdout);
+		for (cpu = 0; ; cpu++) {
+			sprintf(fname, "%s.blktrace.%d", devname, cpu);
+			if (stat(fname, &st) < 0) {
+				if (cpu == 0) {
+					fprintf(stderr, "No tracefiles found for %s\n",
+						devname);
+					rval = 1;
+				}
+				break;
+			}
+			printf("    CPU %d ", cpu); fflush(stdout);
+			nbad = process(&ofp, devname, fname, cpu);
+			if (nbad) {
+				printf("-- %d bad", nbad);
+				rval = 1;
+			}
+			printf("\n");
+		}
+		if (ofp) {
+			fclose(ofp);
+			fprintf(stdout, "Wrote output to %s\n", ofname);
+		}
+	}
+
+	return rval;
+}
diff --git a/blktrace.c b/blktrace.c
new file mode 100644
index 0000000..3c8fb4c
--- /dev/null
+++ b/blktrace.c
@@ -0,0 +1,2715 @@
+/*
+ * block queue tracing application
+ *
+ * Copyright (C) 2005 Jens Axboe <axboe@suse.de>
+ * Copyright (C) 2006 Jens Axboe <axboe@kernel.dk>
+ *
+ * Rewrite to have a single thread per CPU (managing all devices on that CPU)
+ *	Alan D. Brunelle <alan.brunelle@hp.com> - January 2009
+ *
+ *  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
+ *
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <sched.h>
+#include <unistd.h>
+#include <poll.h>
+#include <signal.h>
+#include <pthread.h>
+#include <locale.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/sendfile.h>
+
+#include "btt/list.h"
+#include "blktrace.h"
+
+/*
+ * You may want to increase this even more, if you are logging at a high
+ * rate and see skipped/missed events
+ */
+#define BUF_SIZE		(512 * 1024)
+#define BUF_NR			(4)
+
+#define FILE_VBUF_SIZE		(128 * 1024)
+
+#define DEBUGFS_TYPE		(0x64626720)
+#define TRACE_NET_PORT		(8462)
+
+enum {
+	Net_none = 0,
+	Net_server,
+	Net_client,
+};
+
+enum thread_status {
+	Th_running,
+	Th_leaving,
+	Th_error
+};
+
+/*
+ * Generic stats collected: nevents can be _roughly_ estimated by data_read
+ * (discounting pdu...)
+ *
+ * These fields are updated w/ pdc_dr_update & pdc_nev_update below.
+ */
+struct pdc_stats {
+	unsigned long long data_read;
+	unsigned long long nevents;
+};
+
+struct devpath {
+	struct list_head head;
+	char *path;			/* path to device special file */
+	char *buts_name;		/* name returned from bt kernel code */
+	struct pdc_stats *stats;
+	int fd, ncpus;
+	unsigned long long drops;
+
+	/*
+	 * For piped output only:
+	 *
+	 * Each tracer will have a tracer_devpath_head that it will add new
+	 * data onto. It's list is protected above (tracer_devpath_head.mutex)
+	 * and it will signal the processing thread using the dp_cond,
+	 * dp_mutex & dp_entries variables above.
+	 */
+	struct tracer_devpath_head *heads;
+
+	/*
+	 * For network server mode only:
+	 */
+	struct cl_host *ch;
+	u32 cl_id;
+	time_t cl_connect_time;
+	struct io_info *ios;
+};
+
+/*
+ * For piped output to stdout we will have each tracer thread (one per dev)
+ * tack buffers read from the relay queues on a per-device list.
+ *
+ * The main thread will then collect trace buffers from each of lists in turn.
+ *
+ * We will use a mutex to guard each of the trace_buf list. The tracers
+ * can then signal the main thread using <dp_cond,dp_mutex> and
+ * dp_entries. (When dp_entries is 0, and a tracer adds an entry it will
+ * signal. When dp_entries is 0, the main thread will wait for that condition
+ * to be signalled.)
+ *
+ * adb: It may be better just to have a large buffer per tracer per dev,
+ * and then use it as a ring-buffer. This would certainly cut down a lot
+ * of malloc/free thrashing, at the cost of more memory movements (potentially).
+ */
+struct trace_buf {
+	struct list_head head;
+	struct devpath *dpp;
+	void *buf;
+	int cpu, len;
+};
+
+struct tracer_devpath_head {
+	pthread_mutex_t mutex;
+	struct list_head head;
+	struct trace_buf *prev;
+};
+
+/*
+ * Used to handle the mmap() interfaces for output file (containing traces)
+ */
+struct mmap_info {
+	void *fs_buf;
+	unsigned long long fs_size, fs_max_size, fs_off, fs_buf_len;
+	unsigned long buf_size, buf_nr;
+	int pagesize;
+};
+
+/*
+ * Each thread doing work on a (client) side of blktrace will have one
+ * of these. The ios array contains input/output information, pfds holds
+ * poll() data. The volatile's provide flags to/from the main executing
+ * thread.
+ */
+struct tracer {
+	struct list_head head;
+	struct io_info *ios;
+	struct pollfd *pfds;
+	pthread_t thread;
+	int cpu, nios;
+	volatile int status, is_done;
+};
+
+/*
+ * networking stuff follows. we include a magic number so we know whether
+ * to endianness convert or not.
+ *
+ * The len field is overloaded:
+ *	0 - Indicates an "open" - allowing the server to set up for a dev/cpu
+ *	1 - Indicates a "close" - Shut down connection orderly
+ *
+ * The cpu field is overloaded on close: it will contain the number of drops.
+ */
+struct blktrace_net_hdr {
+	u32 magic;		/* same as trace magic */
+	char buts_name[32];	/* trace name */
+	u32 cpu;		/* for which cpu */
+	u32 max_cpus;
+	u32 len;		/* length of following trace data */
+	u32 cl_id;		/* id for set of client per-cpu connections */
+	u32 buf_size;		/* client buf_size for this trace  */
+	u32 buf_nr;		/* client buf_nr for this trace  */
+	u32 page_size;		/* client page_size for this trace  */
+};
+
+/*
+ * Each host encountered has one of these. The head is used to link this
+ * on to the network server's ch_list. Connections associated with this
+ * host are linked on conn_list, and any devices traced on that host
+ * are connected on the devpaths list.
+ */
+struct cl_host {
+	struct list_head head;
+	struct list_head conn_list;
+	struct list_head devpaths;
+	struct net_server_s *ns;
+	char *hostname;
+	struct in_addr cl_in_addr;
+	int connects, ndevs, cl_opens;
+};
+
+/*
+ * Each connection (client to server socket ('fd')) has one of these. A
+ * back reference to the host ('ch'), and lists headers (for the host
+ * list, and the network server conn_list) are also included.
+ */
+struct cl_conn {
+	struct list_head ch_head, ns_head;
+	struct cl_host *ch;
+	int fd, ncpus;
+	time_t connect_time;
+};
+
+/*
+ * The network server requires some poll structures to be maintained -
+ * one per conection currently on conn_list. The nchs/ch_list values
+ * are for each host connected to this server. The addr field is used
+ * for scratch as new connections are established.
+ */
+struct net_server_s {
+	struct list_head conn_list;
+	struct list_head ch_list;
+	struct pollfd *pfds;
+	int listen_fd, connects, nchs;
+	struct sockaddr_in addr;
+};
+
+/*
+ * This structure is (generically) used to providide information
+ * for a read-to-write set of values.
+ *
+ * ifn & ifd represent input information
+ *
+ * ofn, ofd, ofp, obuf & mmap_info are used for output file (optionally).
+ */
+struct io_info {
+	struct devpath *dpp;
+	FILE *ofp;
+	char *obuf;
+	struct cl_conn *nc;	/* Server network connection */
+
+	/*
+	 * mmap controlled output files
+	 */
+	struct mmap_info mmap_info;
+
+	/*
+	 * Client network fields
+	 */
+	unsigned int ready;
+	unsigned long long data_queued;
+
+	/*
+	 * Input/output file descriptors & names
+	 */
+	int ifd, ofd;
+	char ifn[MAXPATHLEN + 64];
+	char ofn[MAXPATHLEN + 64];
+};
+
+static char blktrace_version[] = "2.0.0";
+
+/*
+ * Linkage to blktrace helper routines (trace conversions)
+ */
+int data_is_native = -1;
+
+static int ndevs;
+static int ncpus;
+static int pagesize;
+static int act_mask = ~0U;
+static int kill_running_trace;
+static int stop_watch;
+static int piped_output;
+
+static char *debugfs_path = "/sys/kernel/debug";
+static char *output_name;
+static char *output_dir;
+
+static unsigned long buf_size = BUF_SIZE;
+static unsigned long buf_nr = BUF_NR;
+
+static FILE *pfp;
+
+static LIST_HEAD(devpaths);
+static LIST_HEAD(tracers);
+
+static volatile int done;
+
+/*
+ * tracer threads add entries, the main thread takes them off and processes
+ * them. These protect the dp_entries variable.
+ */
+static pthread_cond_t dp_cond = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t dp_mutex = PTHREAD_MUTEX_INITIALIZER;
+static volatile int dp_entries;
+
+/*
+ * These synchronize master / thread interactions.
+ */
+static pthread_cond_t mt_cond = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t mt_mutex = PTHREAD_MUTEX_INITIALIZER;
+static volatile int nthreads_running;
+static volatile int nthreads_leaving;
+static volatile int nthreads_error;
+static volatile int tracers_run;
+
+/*
+ * network cmd line params
+ */
+static struct sockaddr_in hostname_addr;
+static char hostname[MAXHOSTNAMELEN];
+static int net_port = TRACE_NET_PORT;
+static int net_use_sendfile = 1;
+static int net_mode;
+static int *cl_fds;
+
+static int (*handle_pfds)(struct tracer *, int, int);
+static int (*handle_list)(struct tracer_devpath_head *, struct list_head *);
+
+#define S_OPTS	"d:a:A:r:o:kw:vVb:n:D:lh:p:sI:"
+static struct option l_opts[] = {
+	{
+		.name = "dev",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'd'
+	},
+	{
+		.name = "input-devs",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'I'
+	},
+	{
+		.name = "act-mask",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'a'
+	},
+	{
+		.name = "set-mask",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'A'
+	},
+	{
+		.name = "relay",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'r'
+	},
+	{
+		.name = "output",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'o'
+	},
+	{
+		.name = "kill",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'k'
+	},
+	{
+		.name = "stopwatch",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'w'
+	},
+	{
+		.name = "version",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'v'
+	},
+	{
+		.name = "version",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'V'
+	},
+	{
+		.name = "buffer-size",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'b'
+	},
+	{
+		.name = "num-sub-buffers",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'n'
+	},
+	{
+		.name = "output-dir",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'D'
+	},
+	{
+		.name = "listen",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'l'
+	},
+	{
+		.name = "host",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'h'
+	},
+	{
+		.name = "port",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'p'
+	},
+	{
+		.name = "no-sendfile",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 's'
+	},
+	{
+		.name = NULL,
+	}
+};
+
+static char usage_str[] = "\n\n" \
+	"-d <dev>             | --dev=<dev>\n" \
+        "[ -r <debugfs path>  | --relay=<debugfs path> ]\n" \
+        "[ -o <file>          | --output=<file>]\n" \
+        "[ -D <dir>           | --output-dir=<dir>\n" \
+        "[ -w <time>          | --stopwatch=<time>]\n" \
+        "[ -a <action field>  | --act-mask=<action field>]\n" \
+        "[ -A <action mask>   | --set-mask=<action mask>]\n" \
+        "[ -b <size>          | --buffer-size]\n" \
+        "[ -n <number>        | --num-sub-buffers=<number>]\n" \
+        "[ -l                 | --listen]\n" \
+        "[ -h <hostname>      | --host=<hostname>]\n" \
+        "[ -p <port number>   | --port=<port number>]\n" \
+        "[ -s                 | --no-sendfile]\n" \
+        "[ -I <devs file>     | --input-devs=<devs file>]\n" \
+        "[ -v <version>       | --version]\n" \
+        "[ -V <version>       | --version]\n" \
+
+	"\t-d Use specified device. May also be given last after options\n" \
+	"\t-r Path to mounted debugfs, defaults to /sys/kernel/debug\n" \
+	"\t-o File(s) to send output to\n" \
+	"\t-D Directory to prepend to output file names\n" \
+	"\t-w Stop after defined time, in seconds\n" \
+	"\t-a Only trace specified actions. See documentation\n" \
+	"\t-A Give trace mask as a single value. See documentation\n" \
+	"\t-b Sub buffer size in KiB (default 512)\n" \
+	"\t-n Number of sub buffers (default 4)\n" \
+	"\t-l Run in network listen mode (blktrace server)\n" \
+	"\t-h Run in network client mode, connecting to the given host\n" \
+	"\t-p Network port to use (default 8462)\n" \
+	"\t-s Make the network client NOT use sendfile() to transfer data\n" \
+	"\t-I Add devices found in <devs file>\n" \
+	"\t-v Print program version info\n" \
+	"\t-V Print program version info\n\n";
+
+static void clear_events(struct pollfd *pfd)
+{
+	pfd->events = 0;
+	pfd->revents = 0;
+}
+
+static inline int net_client_use_sendfile(void)
+{
+	return net_mode == Net_client && net_use_sendfile;
+}
+
+static inline int net_client_use_send(void)
+{
+	return net_mode == Net_client && !net_use_sendfile;
+}
+
+static inline int use_tracer_devpaths(void)
+{
+	return piped_output || net_client_use_send();
+}
+
+static inline int in_addr_eq(struct in_addr a, struct in_addr b)
+{
+	return a.s_addr == b.s_addr;
+}
+
+static inline void pdc_dr_update(struct devpath *dpp, int cpu, int data_read)
+{
+	dpp->stats[cpu].data_read += data_read;
+}
+
+static inline void pdc_nev_update(struct devpath *dpp, int cpu, int nevents)
+{
+	dpp->stats[cpu].nevents += nevents;
+}
+
+static void show_usage(char *prog)
+{
+	fprintf(stderr, "Usage: %s %s", prog, usage_str);
+}
+
+/*
+ * Create a timespec 'msec' milliseconds into the future
+ */
+static inline void make_timespec(struct timespec *tsp, long delta_msec)
+{
+	struct timeval now;
+
+	gettimeofday(&now, NULL);
+	tsp->tv_sec = now.tv_sec;
+	tsp->tv_nsec = 1000L * now.tv_usec;
+
+	tsp->tv_nsec += (delta_msec * 1000000L);
+	if (tsp->tv_nsec > 1000000000L) {
+		long secs = tsp->tv_nsec / 1000000000L;
+
+		tsp->tv_sec += secs;
+		tsp->tv_nsec -= (secs * 1000000000L);
+	}
+}
+
+/*
+ * Add a timer to ensure wait ends
+ */
+static void t_pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
+{
+	struct timespec ts;
+
+	make_timespec(&ts, 50);
+	pthread_cond_timedwait(cond, mutex, &ts);
+}
+
+static void unblock_tracers(void)
+{
+	pthread_mutex_lock(&mt_mutex);
+	tracers_run = 1;
+	pthread_cond_broadcast(&mt_cond);
+	pthread_mutex_unlock(&mt_mutex);
+}
+
+static void tracer_wait_unblock(struct tracer *tp)
+{
+	pthread_mutex_lock(&mt_mutex);
+	while (!tp->is_done && !tracers_run)
+		pthread_cond_wait(&mt_cond, &mt_mutex);
+	pthread_mutex_unlock(&mt_mutex);
+}
+
+static void tracer_signal_ready(struct tracer *tp,
+				enum thread_status th_status,
+				int status)
+{
+	pthread_mutex_lock(&mt_mutex);
+	tp->status = status;
+
+	if (th_status == Th_running)
+		nthreads_running++;
+	else if (th_status == Th_error)
+		nthreads_error++;
+	else
+		nthreads_leaving++;
+
+	pthread_cond_signal(&mt_cond);
+	pthread_mutex_unlock(&mt_mutex);
+}
+
+static void wait_tracers_ready(int ncpus_started)
+{
+	pthread_mutex_lock(&mt_mutex);
+	while ((nthreads_running + nthreads_error) < ncpus_started)
+		t_pthread_cond_wait(&mt_cond, &mt_mutex);
+	pthread_mutex_unlock(&mt_mutex);
+}
+
+static void wait_tracers_leaving(void)
+{
+	pthread_mutex_lock(&mt_mutex);
+	while (nthreads_leaving < nthreads_running)
+		t_pthread_cond_wait(&mt_cond, &mt_mutex);
+	pthread_mutex_unlock(&mt_mutex);
+}
+
+static void init_mmap_info(struct mmap_info *mip)
+{
+	mip->buf_size = buf_size;
+	mip->buf_nr = buf_nr;
+	mip->pagesize = pagesize;
+}
+
+static void net_close_connection(int *fd)
+{
+	shutdown(*fd, SHUT_RDWR);
+	close(*fd);
+	*fd = -1;
+}
+
+static void dpp_free(struct devpath *dpp)
+{
+	if (dpp->stats)
+		free(dpp->stats);
+	if (dpp->ios)
+		free(dpp->ios);
+	if (dpp->path)
+		free(dpp->path);
+	if (dpp->buts_name)
+		free(dpp->buts_name);
+	free(dpp);
+}
+
+static int lock_on_cpu(int cpu)
+{
+	cpu_set_t * cpu_mask;
+	size_t size;
+	cpu_mask = CPU_ALLOC(ncpus);
+	size = CPU_ALLOC_SIZE(ncpus);
+
+	CPU_ZERO_S(size, cpu_mask);
+	CPU_SET_S(cpu, size, cpu_mask);
+	if (sched_setaffinity(0, size, cpu_mask) < 0) {
+		CPU_FREE(cpu_mask);		
+		return errno;
+	}
+
+	CPU_FREE(cpu_mask);		
+	return 0;
+}
+
+static int increase_limit(int resource, rlim_t increase)
+{
+	struct rlimit rlim;
+	int save_errno = errno;
+
+	if (!getrlimit(resource, &rlim)) {
+		rlim.rlim_cur += increase;
+		if (rlim.rlim_cur >= rlim.rlim_max)
+			rlim.rlim_max = rlim.rlim_cur + increase;
+
+		if (!setrlimit(resource, &rlim))
+			return 1;
+	}
+
+	errno = save_errno;
+	return 0;
+}
+
+static int handle_open_failure(void)
+{
+	if (errno == ENFILE || errno == EMFILE)
+		return increase_limit(RLIMIT_NOFILE, 16);
+	return 0;
+}
+
+static int handle_mem_failure(size_t length)
+{
+	if (errno == ENFILE)
+		return handle_open_failure();
+	else if (errno == ENOMEM)
+		return increase_limit(RLIMIT_MEMLOCK, 2 * length);
+	return 0;
+}
+
+static FILE *my_fopen(const char *path, const char *mode)
+{
+	FILE *fp;
+
+	do {
+		fp = fopen(path, mode);
+	} while (fp == NULL && handle_open_failure());
+
+	return fp;
+}
+
+static int my_open(const char *path, int flags)
+{
+	int fd;
+
+	do {
+		fd = open(path, flags);
+	} while (fd < 0 && handle_open_failure());
+
+	return fd;
+}
+
+static int my_socket(int domain, int type, int protocol)
+{
+	int fd;
+
+	do {
+		fd = socket(domain, type, protocol);
+	} while (fd < 0 && handle_open_failure());
+
+	return fd;
+}
+
+static int my_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
+{
+	int fd;
+
+	do {
+		fd = accept(sockfd, addr, addrlen);
+	} while (fd < 0 && handle_open_failure());
+
+	return fd;
+}
+
+static void *my_mmap(void *addr, size_t length, int prot, int flags, int fd,
+		     off_t offset)
+{
+	void *new;
+
+	do {
+		new = mmap(addr, length, prot, flags, fd, offset);
+	} while (new == MAP_FAILED && handle_mem_failure(length));
+
+	return new;
+}
+
+static int my_mlock(struct tracer *tp,
+		    const void *addr, size_t len)
+{
+	int ret, retry = 0;
+
+	do {
+		ret = mlock(addr, len);
+		if ((retry >= 10) && tp && tp->is_done)
+			break;
+		retry++;
+	} while (ret < 0 && handle_mem_failure(len));
+
+	return ret;
+}
+
+static int setup_mmap(int fd, unsigned int maxlen,
+		      struct mmap_info *mip,
+		      struct tracer *tp)
+{
+	if (mip->fs_off + maxlen > mip->fs_buf_len) {
+		unsigned long nr = max(16, mip->buf_nr);
+
+		if (mip->fs_buf) {
+			munlock(mip->fs_buf, mip->fs_buf_len);
+			munmap(mip->fs_buf, mip->fs_buf_len);
+			mip->fs_buf = NULL;
+		}
+
+		mip->fs_off = mip->fs_size & (mip->pagesize - 1);
+		mip->fs_buf_len = (nr * mip->buf_size) - mip->fs_off;
+		mip->fs_max_size += mip->fs_buf_len;
+
+		if (ftruncate(fd, mip->fs_max_size) < 0) {
+			perror("setup_mmap: ftruncate");
+			return 1;
+		}
+
+		mip->fs_buf = my_mmap(NULL, mip->fs_buf_len, PROT_WRITE,
+				      MAP_SHARED, fd,
+				      mip->fs_size - mip->fs_off);
+		if (mip->fs_buf == MAP_FAILED) {
+			perror("setup_mmap: mmap");
+			return 1;
+		}
+		if (my_mlock(tp, mip->fs_buf, mip->fs_buf_len) < 0) {
+			perror("setup_mlock: mlock");
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static int __stop_trace(int fd)
+{
+	/*
+	 * Should be stopped, don't complain if it isn't
+	 */
+	ioctl(fd, BLKTRACESTOP);
+	return ioctl(fd, BLKTRACETEARDOWN);
+}
+
+static int write_data(char *buf, int len)
+{
+	int ret;
+
+rewrite:
+	ret = fwrite(buf, len, 1, pfp);
+	if (ferror(pfp) || ret != 1) {
+		if (errno == EINTR) {
+			clearerr(pfp);
+			goto rewrite;
+		}
+
+		if (!piped_output || (errno != EPIPE && errno != EBADF)) {
+			fprintf(stderr, "write(%d) failed: %d/%s\n",
+				len, errno, strerror(errno));
+		}
+		goto err;
+	}
+
+	fflush(pfp);
+	return 0;
+
+err:
+	clearerr(pfp);
+	return 1;
+}
+
+/*
+ * Returns the number of bytes read (successfully)
+ */
+static int __net_recv_data(int fd, void *buf, unsigned int len)
+{
+	unsigned int bytes_left = len;
+
+	while (bytes_left && !done) {
+		int ret = recv(fd, buf, bytes_left, MSG_WAITALL);
+
+		if (ret == 0)
+			break;
+		else if (ret < 0) {
+			if (errno == EAGAIN) {
+				usleep(50);
+				continue;
+			}
+			perror("server: net_recv_data: recv failed");
+			break;
+		} else {
+			buf += ret;
+			bytes_left -= ret;
+		}
+	}
+
+	return len - bytes_left;
+}
+
+static int net_recv_data(int fd, void *buf, unsigned int len)
+{
+	return __net_recv_data(fd, buf, len);
+}
+
+/*
+ * Returns number of bytes written
+ */
+static int net_send_data(int fd, void *buf, unsigned int buf_len)
+{
+	int ret;
+	unsigned int bytes_left = buf_len;
+
+	while (bytes_left) {
+		ret = send(fd, buf, bytes_left, 0);
+		if (ret < 0) {
+			perror("send");
+			break;
+		}
+
+		buf += ret;
+		bytes_left -= ret;
+	}
+
+	return buf_len - bytes_left;
+}
+
+static int net_send_header(int fd, int cpu, char *buts_name, int len)
+{
+	struct blktrace_net_hdr hdr;
+
+	memset(&hdr, 0, sizeof(hdr));
+
+	hdr.magic = BLK_IO_TRACE_MAGIC;
+	memset(hdr.buts_name, 0, sizeof(hdr.buts_name));
+	strncpy(hdr.buts_name, buts_name, sizeof(hdr.buts_name));
+	hdr.buts_name[sizeof(hdr.buts_name) - 1] = '\0';
+	hdr.cpu = cpu;
+	hdr.max_cpus = ncpus;
+	hdr.len = len;
+	hdr.cl_id = getpid();
+	hdr.buf_size = buf_size;
+	hdr.buf_nr = buf_nr;
+	hdr.page_size = pagesize;
+
+	return net_send_data(fd, &hdr, sizeof(hdr)) != sizeof(hdr);
+}
+
+static void net_send_open_close(int fd, int cpu, char *buts_name, int len)
+{
+	struct blktrace_net_hdr ret_hdr;
+
+	net_send_header(fd, cpu, buts_name, len);
+	net_recv_data(fd, &ret_hdr, sizeof(ret_hdr));
+}
+
+static void net_send_open(int fd, int cpu, char *buts_name)
+{
+	net_send_open_close(fd, cpu, buts_name, 0);
+}
+
+static void net_send_close(int fd, char *buts_name, int drops)
+{
+	/*
+	 * Overload CPU w/ number of drops
+	 *
+	 * XXX: Need to clear/set done around call - done=1 (which
+	 * is true here) stops reads from happening... :-(
+	 */
+	done = 0;
+	net_send_open_close(fd, drops, buts_name, 1);
+	done = 1;
+}
+
+static void ack_open_close(int fd, char *buts_name)
+{
+	net_send_header(fd, 0, buts_name, 2);
+}
+
+static void net_send_drops(int fd)
+{
+	struct list_head *p;
+
+	__list_for_each(p, &devpaths) {
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+
+		net_send_close(fd, dpp->buts_name, dpp->drops);
+	}
+}
+
+/*
+ * Returns:
+ *	 0: "EOF"
+ *	 1: OK
+ *	-1: Error
+ */
+static int net_get_header(struct cl_conn *nc, struct blktrace_net_hdr *bnh)
+{
+	int bytes_read;
+	int fl = fcntl(nc->fd, F_GETFL);
+
+	fcntl(nc->fd, F_SETFL, fl | O_NONBLOCK);
+	bytes_read = __net_recv_data(nc->fd, bnh, sizeof(*bnh));
+	fcntl(nc->fd, F_SETFL, fl & ~O_NONBLOCK);
+
+	if (bytes_read == sizeof(*bnh))
+		return 1;
+	else if (bytes_read == 0)
+		return 0;
+	else
+		return -1;
+}
+
+static int net_setup_addr(void)
+{
+	struct sockaddr_in *addr = &hostname_addr;
+
+	memset(addr, 0, sizeof(*addr));
+	addr->sin_family = AF_INET;
+	addr->sin_port = htons(net_port);
+
+	if (inet_aton(hostname, &addr->sin_addr) != 1) {
+		struct hostent *hent;
+retry:
+		hent = gethostbyname(hostname);
+		if (!hent) {
+			if (h_errno == TRY_AGAIN) {
+				usleep(100);
+				goto retry;
+			} else if (h_errno == NO_RECOVERY) {
+				fprintf(stderr, "gethostbyname(%s)"
+					"non-recoverable error encountered\n",
+					hostname);
+			} else {
+				/*
+				 * HOST_NOT_FOUND, NO_ADDRESS or NO_DATA
+				 */
+				fprintf(stderr, "Host %s not found\n",
+					hostname);
+			}
+			return 1;
+		}
+
+		memcpy(&addr->sin_addr, hent->h_addr, 4);
+		memset(hostname, 0, sizeof(hostname));
+		strncpy(hostname, hent->h_name, sizeof(hostname));
+		hostname[sizeof(hostname) - 1] = '\0';
+	}
+
+	return 0;
+}
+
+static int net_setup_client(void)
+{
+	int fd;
+	struct sockaddr_in *addr = &hostname_addr;
+
+	fd = my_socket(AF_INET, SOCK_STREAM, 0);
+	if (fd < 0) {
+		perror("client: socket");
+		return -1;
+	}
+
+	if (connect(fd, (struct sockaddr *)addr, sizeof(*addr)) < 0) {
+		if (errno == ECONNREFUSED)
+			fprintf(stderr,
+				"\nclient: Connection to %s refused, "
+				"perhaps the server is not started?\n\n",
+				hostname);
+		else
+			perror("client: connect");
+
+		close(fd);
+		return -1;
+	}
+
+	return fd;
+}
+
+static int open_client_connections(void)
+{
+	int cpu;
+
+	cl_fds = calloc(ncpus, sizeof(*cl_fds));
+	for (cpu = 0; cpu < ncpus; cpu++) {
+		cl_fds[cpu] = net_setup_client();
+		if (cl_fds[cpu] < 0)
+			goto err;
+	}
+	return 0;
+
+err:
+	while (cpu > 0)
+		close(cl_fds[cpu--]);
+	free(cl_fds);
+	return 1;
+}
+
+static void close_client_connections(void)
+{
+	if (cl_fds) {
+		int cpu, *fdp;
+
+		for (cpu = 0, fdp = cl_fds; cpu < ncpus; cpu++, fdp++) {
+			if (*fdp >= 0) {
+				net_send_drops(*fdp);
+				net_close_connection(fdp);
+			}
+		}
+		free(cl_fds);
+	}
+}
+
+static void setup_buts(void)
+{
+	struct list_head *p;
+
+	__list_for_each(p, &devpaths) {
+		struct blk_user_trace_setup buts;
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+
+		memset(&buts, 0, sizeof(buts));
+		buts.buf_size = buf_size;
+		buts.buf_nr = buf_nr;
+		buts.act_mask = act_mask;
+
+		if (ioctl(dpp->fd, BLKTRACESETUP, &buts) >= 0) {
+			dpp->ncpus = ncpus;
+			dpp->buts_name = strdup(buts.name);
+			if (dpp->stats)
+				free(dpp->stats);
+			dpp->stats = calloc(dpp->ncpus, sizeof(*dpp->stats));
+			memset(dpp->stats, 0, dpp->ncpus * sizeof(*dpp->stats));
+		} else
+			fprintf(stderr, "BLKTRACESETUP(2) %s failed: %d/%s\n",
+				dpp->path, errno, strerror(errno));
+	}
+}
+
+static void start_buts(void)
+{
+	struct list_head *p;
+
+	__list_for_each(p, &devpaths) {
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+
+		if (ioctl(dpp->fd, BLKTRACESTART) < 0) {
+			fprintf(stderr, "BLKTRACESTART %s failed: %d/%s\n",
+				dpp->path, errno, strerror(errno));
+		}
+	}
+}
+
+static int get_drops(struct devpath *dpp)
+{
+	int fd, drops = 0;
+	char fn[MAXPATHLEN + 64], tmp[256];
+
+	snprintf(fn, sizeof(fn), "%s/block/%s/dropped", debugfs_path,
+		 dpp->buts_name);
+
+	fd = my_open(fn, O_RDONLY);
+	if (fd < 0) {
+		/*
+		 * This may be ok: the kernel may not support
+		 * dropped counts.
+		 */
+		if (errno != ENOENT)
+			fprintf(stderr, "Could not open %s: %d/%s\n",
+				fn, errno, strerror(errno));
+		return 0;
+	} else if (read(fd, tmp, sizeof(tmp)) < 0) {
+		fprintf(stderr, "Could not read %s: %d/%s\n",
+			fn, errno, strerror(errno));
+	} else
+		drops = atoi(tmp);
+	close(fd);
+
+	return drops;
+}
+
+static void get_all_drops(void)
+{
+	struct list_head *p;
+
+	__list_for_each(p, &devpaths) {
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+
+		dpp->drops = get_drops(dpp);
+	}
+}
+
+static inline struct trace_buf *alloc_trace_buf(int cpu, int bufsize)
+{
+	struct trace_buf *tbp;
+
+	tbp = malloc(sizeof(*tbp) + bufsize);
+	INIT_LIST_HEAD(&tbp->head);
+	tbp->len = 0;
+	tbp->buf = (void *)(tbp + 1);
+	tbp->cpu = cpu;
+	tbp->dpp = NULL;	/* Will be set when tbp is added */
+
+	return tbp;
+}
+
+static void free_tracer_heads(struct devpath *dpp)
+{
+	int cpu;
+	struct tracer_devpath_head *hd;
+
+	for (cpu = 0, hd = dpp->heads; cpu < ncpus; cpu++, hd++) {
+		if (hd->prev)
+			free(hd->prev);
+
+		pthread_mutex_destroy(&hd->mutex);
+	}
+	free(dpp->heads);
+}
+
+static int setup_tracer_devpaths(void)
+{
+	struct list_head *p;
+
+	if (net_client_use_send())
+		if (open_client_connections())
+			return 1;
+
+	__list_for_each(p, &devpaths) {
+		int cpu;
+		struct tracer_devpath_head *hd;
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+
+		dpp->heads = calloc(ncpus, sizeof(struct tracer_devpath_head));
+		for (cpu = 0, hd = dpp->heads; cpu < ncpus; cpu++, hd++) {
+			INIT_LIST_HEAD(&hd->head);
+			pthread_mutex_init(&hd->mutex, NULL);
+			hd->prev = NULL;
+		}
+	}
+
+	return 0;
+}
+
+static inline void add_trace_buf(struct devpath *dpp, int cpu,
+						struct trace_buf **tbpp)
+{
+	struct trace_buf *tbp = *tbpp;
+	struct tracer_devpath_head *hd = &dpp->heads[cpu];
+
+	tbp->dpp = dpp;
+
+	pthread_mutex_lock(&hd->mutex);
+	list_add_tail(&tbp->head, &hd->head);
+	pthread_mutex_unlock(&hd->mutex);
+
+	*tbpp = alloc_trace_buf(cpu, buf_size);
+}
+
+static inline void incr_entries(int entries_handled)
+{
+	pthread_mutex_lock(&dp_mutex);
+	if (dp_entries == 0)
+		pthread_cond_signal(&dp_cond);
+	dp_entries += entries_handled;
+	pthread_mutex_unlock(&dp_mutex);
+}
+
+static void decr_entries(int handled)
+{
+	pthread_mutex_lock(&dp_mutex);
+	dp_entries -= handled;
+	pthread_mutex_unlock(&dp_mutex);
+}
+
+static int wait_empty_entries(void)
+{
+	pthread_mutex_lock(&dp_mutex);
+	while (!done && dp_entries == 0)
+		t_pthread_cond_wait(&dp_cond, &dp_mutex);
+	pthread_mutex_unlock(&dp_mutex);
+
+	return !done;
+}
+
+static int add_devpath(char *path)
+{
+	int fd;
+	struct devpath *dpp;
+	struct list_head *p;
+
+	/*
+	 * Verify device is not duplicated
+	 */
+	__list_for_each(p, &devpaths) {
+	       struct devpath *tmp = list_entry(p, struct devpath, head);
+	       if (!strcmp(tmp->path, path))
+		        return 0;
+	}
+	/*
+	 * Verify device is valid before going too far
+	 */
+	fd = my_open(path, O_RDONLY | O_NONBLOCK);
+	if (fd < 0) {
+		fprintf(stderr, "Invalid path %s specified: %d/%s\n",
+			path, errno, strerror(errno));
+		return 1;
+	}
+
+	dpp = malloc(sizeof(*dpp));
+	memset(dpp, 0, sizeof(*dpp));
+	dpp->path = strdup(path);
+	dpp->fd = fd;
+	ndevs++;
+	list_add_tail(&dpp->head, &devpaths);
+
+	return 0;
+}
+
+static void rel_devpaths(void)
+{
+	struct list_head *p, *q;
+
+	list_for_each_safe(p, q, &devpaths) {
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+
+		list_del(&dpp->head);
+		__stop_trace(dpp->fd);
+		close(dpp->fd);
+
+		if (dpp->heads)
+			free_tracer_heads(dpp);
+
+		dpp_free(dpp);
+		ndevs--;
+	}
+}
+
+static int flush_subbuf_net(struct trace_buf *tbp)
+{
+	int fd = cl_fds[tbp->cpu];
+	struct devpath *dpp = tbp->dpp;
+
+	if (net_send_header(fd, tbp->cpu, dpp->buts_name, tbp->len))
+		return 1;
+	else if (net_send_data(fd, tbp->buf, tbp->len) != tbp->len)
+		return 1;
+
+	return 0;
+}
+
+static int
+handle_list_net(__attribute__((__unused__))struct tracer_devpath_head *hd,
+		struct list_head *list)
+{
+	struct trace_buf *tbp;
+	struct list_head *p, *q;
+	int entries_handled = 0;
+
+	list_for_each_safe(p, q, list) {
+		tbp = list_entry(p, struct trace_buf, head);
+
+		list_del(&tbp->head);
+		entries_handled++;
+
+		if (cl_fds[tbp->cpu] >= 0) {
+			if (flush_subbuf_net(tbp)) {
+				close(cl_fds[tbp->cpu]);
+				cl_fds[tbp->cpu] = -1;
+			}
+		}
+
+		free(tbp);
+	}
+
+	return entries_handled;
+}
+
+/*
+ * Tack 'tbp's buf onto the tail of 'prev's buf
+ */
+static struct trace_buf *tb_combine(struct trace_buf *prev,
+				    struct trace_buf *tbp)
+{
+	unsigned long tot_len;
+
+	tot_len = prev->len + tbp->len;
+	if (tot_len > buf_size) {
+		/*
+		 * tbp->head isn't connected (it was 'prev'
+		 * so it had been taken off of the list
+		 * before). Therefore, we can realloc
+		 * the whole structures, as the other fields
+		 * are "static".
+		 */
+		prev = realloc(prev, sizeof(*prev) + tot_len);
+		prev->buf = (void *)(prev + 1);
+	}
+
+	memcpy(prev->buf + prev->len, tbp->buf, tbp->len);
+	prev->len = tot_len;
+
+	free(tbp);
+	return prev;
+}
+
+static int handle_list_file(struct tracer_devpath_head *hd,
+			    struct list_head *list)
+{
+	int off, t_len, nevents;
+	struct blk_io_trace *t;
+	struct list_head *p, *q;
+	int entries_handled = 0;
+	struct trace_buf *tbp, *prev;
+
+	prev = hd->prev;
+	list_for_each_safe(p, q, list) {
+		tbp = list_entry(p, struct trace_buf, head);
+		list_del(&tbp->head);
+		entries_handled++;
+
+		/*
+		 * If there was some leftover before, tack this new
+		 * entry onto the tail of the previous one.
+		 */
+		if (prev)
+			tbp = tb_combine(prev, tbp);
+
+		/*
+		 * See how many whole traces there are - send them
+		 * all out in one go.
+		 */
+		off = 0;
+		nevents = 0;
+		while (off + (int)sizeof(*t) <= tbp->len) {
+			t = (struct blk_io_trace *)(tbp->buf + off);
+			t_len = sizeof(*t) + t->pdu_len;
+			if (off + t_len > tbp->len)
+				break;
+
+			off += t_len;
+			nevents++;
+		}
+		if (nevents)
+			pdc_nev_update(tbp->dpp, tbp->cpu, nevents);
+
+		/*
+		 * Write any full set of traces, any remaining data is kept
+		 * for the next pass.
+		 */
+		if (off) {
+			if (write_data(tbp->buf, off) || off == tbp->len) {
+				free(tbp);
+				prev = NULL;
+			}
+			else {
+				/*
+				 * Move valid data to beginning of buffer
+				 */
+				tbp->len -= off;
+				memmove(tbp->buf, tbp->buf + off, tbp->len);
+				prev = tbp;
+			}
+		} else
+			prev = tbp;
+	}
+	hd->prev = prev;
+
+	return entries_handled;
+}
+
+static void __process_trace_bufs(void)
+{
+	int cpu;
+	struct list_head *p;
+	struct list_head list;
+	int handled = 0;
+
+	__list_for_each(p, &devpaths) {
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+		struct tracer_devpath_head *hd = dpp->heads;
+
+		for (cpu = 0; cpu < ncpus; cpu++, hd++) {
+			pthread_mutex_lock(&hd->mutex);
+			if (list_empty(&hd->head)) {
+				pthread_mutex_unlock(&hd->mutex);
+				continue;
+			}
+
+			list_replace_init(&hd->head, &list);
+			pthread_mutex_unlock(&hd->mutex);
+
+			handled += handle_list(hd, &list);
+		}
+	}
+
+	if (handled)
+		decr_entries(handled);
+}
+
+static void process_trace_bufs(void)
+{
+	while (wait_empty_entries())
+		__process_trace_bufs();
+}
+
+static void clean_trace_bufs(void)
+{
+	/*
+	 * No mutex needed here: we're only reading from the lists,
+	 * tracers are done
+	 */
+	while (dp_entries)
+		__process_trace_bufs();
+}
+
+static inline void read_err(int cpu, char *ifn)
+{
+	if (errno != EAGAIN)
+		fprintf(stderr, "Thread %d failed read of %s: %d/%s\n",
+			cpu, ifn, errno, strerror(errno));
+}
+
+static int net_sendfile(struct io_info *iop)
+{
+	int ret;
+
+	ret = sendfile(iop->ofd, iop->ifd, NULL, iop->ready);
+	if (ret < 0) {
+		perror("sendfile");
+		return 1;
+	} else if (ret < (int)iop->ready) {
+		fprintf(stderr, "short sendfile send (%d of %d)\n",
+			ret, iop->ready);
+		return 1;
+	}
+
+	return 0;
+}
+
+static inline int net_sendfile_data(struct tracer *tp, struct io_info *iop)
+{
+	struct devpath *dpp = iop->dpp;
+
+	if (net_send_header(iop->ofd, tp->cpu, dpp->buts_name, iop->ready))
+		return 1;
+	return net_sendfile(iop);
+}
+
+static int fill_ofname(struct io_info *iop, int cpu)
+{
+	int len;
+	struct stat sb;
+	char *dst = iop->ofn;
+
+	if (output_dir)
+		len = snprintf(iop->ofn, sizeof(iop->ofn), "%s/", output_dir);
+	else
+		len = snprintf(iop->ofn, sizeof(iop->ofn), "./");
+
+	if (net_mode == Net_server) {
+		struct cl_conn *nc = iop->nc;
+
+		len += sprintf(dst + len, "%s-", nc->ch->hostname);
+		len += strftime(dst + len, 64, "%F-%T/",
+				gmtime(&iop->dpp->cl_connect_time));
+	}
+
+	if (stat(iop->ofn, &sb) < 0) {
+		if (errno != ENOENT) {
+			fprintf(stderr,
+				"Destination dir %s stat failed: %d/%s\n",
+				iop->ofn, errno, strerror(errno));
+			return 1;
+		}
+		/*
+		 * There is no synchronization between multiple threads
+		 * trying to create the directory at once.  It's harmless
+		 * to let them try, so just detect the problem and move on.
+		 */
+		if (mkdir(iop->ofn, 0755) < 0 && errno != EEXIST) {
+			fprintf(stderr,
+				"Destination dir %s can't be made: %d/%s\n",
+				iop->ofn, errno, strerror(errno));
+			return 1;
+		}
+	}
+
+	if (output_name)
+		snprintf(iop->ofn + len, sizeof(iop->ofn), "%s.blktrace.%d",
+			 output_name, cpu);
+	else
+		snprintf(iop->ofn + len, sizeof(iop->ofn), "%s.blktrace.%d",
+			 iop->dpp->buts_name, cpu);
+
+	return 0;
+}
+
+static int set_vbuf(struct io_info *iop, int mode, size_t size)
+{
+	iop->obuf = malloc(size);
+	if (setvbuf(iop->ofp, iop->obuf, mode, size) < 0) {
+		fprintf(stderr, "setvbuf(%s, %d) failed: %d/%s\n",
+			iop->dpp->path, (int)size, errno,
+			strerror(errno));
+		free(iop->obuf);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int iop_open(struct io_info *iop, int cpu)
+{
+	iop->ofd = -1;
+	if (fill_ofname(iop, cpu))
+		return 1;
+
+	iop->ofp = my_fopen(iop->ofn, "w+");
+	if (iop->ofp == NULL) {
+		fprintf(stderr, "Open output file %s failed: %d/%s\n",
+			iop->ofn, errno, strerror(errno));
+		return 1;
+	}
+
+	if (set_vbuf(iop, _IOLBF, FILE_VBUF_SIZE)) {
+		fprintf(stderr, "set_vbuf for file %s failed: %d/%s\n",
+			iop->ofn, errno, strerror(errno));
+		fclose(iop->ofp);
+		return 1;
+	}
+
+	iop->ofd = fileno(iop->ofp);
+	return 0;
+}
+
+static void close_iop(struct io_info *iop)
+{
+	struct mmap_info *mip = &iop->mmap_info;
+
+	if (mip->fs_buf)
+		munmap(mip->fs_buf, mip->fs_buf_len);
+
+	if (!piped_output) {
+		if (ftruncate(fileno(iop->ofp), mip->fs_size) < 0) {
+			fprintf(stderr,
+				"Ignoring err: ftruncate(%s): %d/%s\n",
+				iop->ofn, errno, strerror(errno));
+		}
+	}
+
+	if (iop->ofp)
+		fclose(iop->ofp);
+	if (iop->obuf)
+		free(iop->obuf);
+}
+
+static void close_ios(struct tracer *tp)
+{
+	while (tp->nios > 0) {
+		struct io_info *iop = &tp->ios[--tp->nios];
+
+		iop->dpp->drops = get_drops(iop->dpp);
+		if (iop->ifd >= 0)
+			close(iop->ifd);
+
+		if (iop->ofp)
+			close_iop(iop);
+		else if (iop->ofd >= 0) {
+			struct devpath *dpp = iop->dpp;
+
+			net_send_close(iop->ofd, dpp->buts_name, dpp->drops);
+			net_close_connection(&iop->ofd);
+		}
+	}
+
+	free(tp->ios);
+	free(tp->pfds);
+}
+
+static int open_ios(struct tracer *tp)
+{
+	struct pollfd *pfd;
+	struct io_info *iop;
+	struct list_head *p;
+
+	tp->ios = calloc(ndevs, sizeof(struct io_info));
+	memset(tp->ios, 0, ndevs * sizeof(struct io_info));
+
+	tp->pfds = calloc(ndevs, sizeof(struct pollfd));
+	memset(tp->pfds, 0, ndevs * sizeof(struct pollfd));
+
+	tp->nios = 0;
+	iop = tp->ios;
+	pfd = tp->pfds;
+	__list_for_each(p, &devpaths) {
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+
+		iop->dpp = dpp;
+		iop->ofd = -1;
+		snprintf(iop->ifn, sizeof(iop->ifn), "%s/block/%s/trace%d",
+			debugfs_path, dpp->buts_name, tp->cpu);
+
+		iop->ifd = my_open(iop->ifn, O_RDONLY | O_NONBLOCK);
+		if (iop->ifd < 0) {
+			fprintf(stderr, "Thread %d failed open %s: %d/%s\n",
+				tp->cpu, iop->ifn, errno, strerror(errno));
+			return 1;
+		}
+
+		init_mmap_info(&iop->mmap_info);
+
+		pfd->fd = iop->ifd;
+		pfd->events = POLLIN;
+
+		if (piped_output)
+			;
+		else if (net_client_use_sendfile()) {
+			iop->ofd = net_setup_client();
+			if (iop->ofd < 0)
+				goto err;
+			net_send_open(iop->ofd, tp->cpu, dpp->buts_name);
+		} else if (net_mode == Net_none) {
+			if (iop_open(iop, tp->cpu))
+				goto err;
+		} else {
+			/*
+			 * This ensures that the server knows about all
+			 * connections & devices before _any_ closes
+			 */
+			net_send_open(cl_fds[tp->cpu], tp->cpu, dpp->buts_name);
+		}
+
+		pfd++;
+		iop++;
+		tp->nios++;
+	}
+
+	return 0;
+
+err:
+	close(iop->ifd);	/* tp->nios _not_ bumped */
+	close_ios(tp);
+	return 1;
+}
+
+static int handle_pfds_file(struct tracer *tp, int nevs, int force_read)
+{
+	struct mmap_info *mip;
+	int i, ret, nentries = 0;
+	struct pollfd *pfd = tp->pfds;
+	struct io_info *iop = tp->ios;
+
+	for (i = 0; nevs > 0 && i < ndevs; i++, pfd++, iop++) {
+		if (pfd->revents & POLLIN || force_read) {
+			mip = &iop->mmap_info;
+
+			ret = setup_mmap(iop->ofd, buf_size, mip, tp);
+			if (ret < 0) {
+				pfd->events = 0;
+				break;
+			}
+
+			ret = read(iop->ifd, mip->fs_buf + mip->fs_off,
+				   buf_size);
+			if (ret > 0) {
+				pdc_dr_update(iop->dpp, tp->cpu, ret);
+				mip->fs_size += ret;
+				mip->fs_off += ret;
+				nentries++;
+			} else if (ret == 0) {
+				/*
+				 * Short reads after we're done stop us
+				 * from trying reads.
+				 */
+				if (tp->is_done)
+					clear_events(pfd);
+			} else {
+				read_err(tp->cpu, iop->ifn);
+				if (errno != EAGAIN || tp->is_done)
+					clear_events(pfd);
+			}
+			nevs--;
+		}
+	}
+
+	return nentries;
+}
+
+static int handle_pfds_netclient(struct tracer *tp, int nevs, int force_read)
+{
+	struct stat sb;
+	int i, nentries = 0;
+	struct pollfd *pfd = tp->pfds;
+	struct io_info *iop = tp->ios;
+
+	for (i = 0; i < ndevs; i++, pfd++, iop++) {
+		if (pfd->revents & POLLIN || force_read) {
+			if (fstat(iop->ifd, &sb) < 0) {
+				perror(iop->ifn);
+				pfd->events = 0;
+			} else if (sb.st_size > (off_t)iop->data_queued) {
+				iop->ready = sb.st_size - iop->data_queued;
+				iop->data_queued = sb.st_size;
+
+				if (!net_sendfile_data(tp, iop)) {
+					pdc_dr_update(iop->dpp, tp->cpu,
+						      iop->ready);
+					nentries++;
+				} else
+					clear_events(pfd);
+			}
+			if (--nevs == 0)
+				break;
+		}
+	}
+
+	if (nentries)
+		incr_entries(nentries);
+
+	return nentries;
+}
+
+static int handle_pfds_entries(struct tracer *tp, int nevs, int force_read)
+{
+	int i, nentries = 0;
+	struct trace_buf *tbp;
+	struct pollfd *pfd = tp->pfds;
+	struct io_info *iop = tp->ios;
+
+	tbp = alloc_trace_buf(tp->cpu, buf_size);
+	for (i = 0; i < ndevs; i++, pfd++, iop++) {
+		if (pfd->revents & POLLIN || force_read) {
+			tbp->len = read(iop->ifd, tbp->buf, buf_size);
+			if (tbp->len > 0) {
+				pdc_dr_update(iop->dpp, tp->cpu, tbp->len);
+				add_trace_buf(iop->dpp, tp->cpu, &tbp);
+				nentries++;
+			} else if (tbp->len == 0) {
+				/*
+				 * Short reads after we're done stop us
+				 * from trying reads.
+				 */
+				if (tp->is_done)
+					clear_events(pfd);
+			} else {
+				read_err(tp->cpu, iop->ifn);
+				if (errno != EAGAIN || tp->is_done)
+					clear_events(pfd);
+			}
+			if (!piped_output && --nevs == 0)
+				break;
+		}
+	}
+	free(tbp);
+
+	if (nentries)
+		incr_entries(nentries);
+
+	return nentries;
+}
+
+static void *thread_main(void *arg)
+{
+	int ret, ndone, to_val;
+	struct tracer *tp = arg;
+
+	ret = lock_on_cpu(tp->cpu);
+	if (ret)
+		goto err;
+
+	ret = open_ios(tp);
+	if (ret)
+		goto err;
+
+	if (piped_output)
+		to_val = 50;		/* Frequent partial handles */
+	else
+		to_val = 500;		/* 1/2 second intervals */
+
+
+	tracer_signal_ready(tp, Th_running, 0);
+	tracer_wait_unblock(tp);
+
+	while (!tp->is_done) {
+		ndone = poll(tp->pfds, ndevs, to_val);
+		if (ndone || piped_output)
+			(void)handle_pfds(tp, ndone, piped_output);
+		else if (ndone < 0 && errno != EINTR)
+			fprintf(stderr, "Thread %d poll failed: %d/%s\n",
+				tp->cpu, errno, strerror(errno));
+	}
+
+	/*
+	 * Trace is stopped, pull data until we get a short read
+	 */
+	while (handle_pfds(tp, ndevs, 1) > 0)
+		;
+
+	close_ios(tp);
+	tracer_signal_ready(tp, Th_leaving, 0);
+	return NULL;
+
+err:
+	tracer_signal_ready(tp, Th_error, ret);
+	return NULL;
+}
+
+static int start_tracer(int cpu)
+{
+	struct tracer *tp;
+
+	tp = malloc(sizeof(*tp));
+	memset(tp, 0, sizeof(*tp));
+
+	INIT_LIST_HEAD(&tp->head);
+	tp->status = 0;
+	tp->cpu = cpu;
+
+	if (pthread_create(&tp->thread, NULL, thread_main, tp)) {
+		fprintf(stderr, "FAILED to start thread on CPU %d: %d/%s\n",
+			cpu, errno, strerror(errno));
+		free(tp);
+		return 1;
+	}
+
+	list_add_tail(&tp->head, &tracers);
+	return 0;
+}
+
+static void start_tracers(void)
+{
+	int cpu;
+	struct list_head *p;
+
+	for (cpu = 0; cpu < ncpus; cpu++)
+		if (start_tracer(cpu))
+			break;
+
+	wait_tracers_ready(cpu);
+
+	__list_for_each(p, &tracers) {
+		struct tracer *tp = list_entry(p, struct tracer, head);
+		if (tp->status)
+			fprintf(stderr,
+				"FAILED to start thread on CPU %d: %d/%s\n",
+				tp->cpu, tp->status, strerror(tp->status));
+	}
+}
+
+static void stop_tracers(void)
+{
+	struct list_head *p;
+
+	/*
+	 * Stop the tracing - makes the tracer threads clean up quicker.
+	 */
+	__list_for_each(p, &devpaths) {
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+		(void)ioctl(dpp->fd, BLKTRACESTOP);
+	}
+
+	/*
+	 * Tell each tracer to quit
+	 */
+	__list_for_each(p, &tracers) {
+		struct tracer *tp = list_entry(p, struct tracer, head);
+		tp->is_done = 1;
+	}
+	pthread_cond_broadcast(&mt_cond);
+}
+
+static void del_tracers(void)
+{
+	struct list_head *p, *q;
+
+	list_for_each_safe(p, q, &tracers) {
+		struct tracer *tp = list_entry(p, struct tracer, head);
+
+		list_del(&tp->head);
+		free(tp);
+	}
+}
+
+static void wait_tracers(void)
+{
+	struct list_head *p;
+
+	if (use_tracer_devpaths())
+		process_trace_bufs();
+
+	wait_tracers_leaving();
+
+	__list_for_each(p, &tracers) {
+		int ret;
+		struct tracer *tp = list_entry(p, struct tracer, head);
+
+		ret = pthread_join(tp->thread, NULL);
+		if (ret)
+			fprintf(stderr, "Thread join %d failed %d\n",
+				tp->cpu, ret);
+	}
+
+	if (use_tracer_devpaths())
+		clean_trace_bufs();
+
+	get_all_drops();
+}
+
+static void exit_tracing(void)
+{
+	signal(SIGINT, SIG_IGN);
+	signal(SIGHUP, SIG_IGN);
+	signal(SIGTERM, SIG_IGN);
+	signal(SIGALRM, SIG_IGN);
+
+	stop_tracers();
+	wait_tracers();
+	del_tracers();
+	rel_devpaths();
+}
+
+static void handle_sigint(__attribute__((__unused__)) int sig)
+{
+	done = 1;
+	stop_tracers();
+}
+
+static void show_stats(struct list_head *devpaths)
+{
+	FILE *ofp;
+	struct list_head *p;
+	unsigned long long nevents, data_read;
+	unsigned long long total_drops = 0;
+	unsigned long long total_events = 0;
+
+	if (piped_output)
+		ofp = my_fopen("/dev/null", "w");
+	else
+		ofp = stdout;
+
+	__list_for_each(p, devpaths) {
+		int cpu;
+		struct pdc_stats *sp;
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+
+		if (net_mode == Net_server)
+			printf("server: end of run for %s:%s\n",
+				dpp->ch->hostname, dpp->buts_name);
+
+		data_read = 0;
+		nevents = 0;
+
+		fprintf(ofp, "=== %s ===\n", dpp->buts_name);
+		for (cpu = 0, sp = dpp->stats; cpu < dpp->ncpus; cpu++, sp++) {
+			/*
+			 * Estimate events if not known...
+			 */
+			if (sp->nevents == 0) {
+				sp->nevents = sp->data_read /
+						sizeof(struct blk_io_trace);
+			}
+
+			fprintf(ofp,
+				"  CPU%3d: %20llu events, %8llu KiB data\n",
+				cpu, sp->nevents, (sp->data_read + 1023) >> 10);
+
+			data_read += sp->data_read;
+			nevents += sp->nevents;
+		}
+
+		fprintf(ofp, "  Total:  %20llu events (dropped %llu),"
+			     " %8llu KiB data\n", nevents,
+			     dpp->drops, (data_read + 1024) >> 10);
+
+		total_drops += dpp->drops;
+		total_events += (nevents + dpp->drops);
+	}
+
+	fflush(ofp);
+	if (piped_output)
+		fclose(ofp);
+
+	if (total_drops) {
+		double drops_ratio = 1.0;
+
+		if (total_events)
+			drops_ratio = (double)total_drops/(double)total_events;
+
+		fprintf(stderr, "\nYou have %llu (%5.1lf%%) dropped events\n"
+				"Consider using a larger buffer size (-b) "
+				"and/or more buffers (-n)\n",
+			total_drops, 100.0 * drops_ratio);
+	}
+}
+
+static int handle_args(int argc, char *argv[])
+{
+	int c, i;
+	struct statfs st;
+	int act_mask_tmp = 0;
+
+	while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) >= 0) {
+		switch (c) {
+		case 'a':
+			i = find_mask_map(optarg);
+			if (i < 0) {
+				fprintf(stderr, "Invalid action mask %s\n",
+					optarg);
+				return 1;
+			}
+			act_mask_tmp |= i;
+			break;
+
+		case 'A':
+			if ((sscanf(optarg, "%x", &i) != 1) ||
+							!valid_act_opt(i)) {
+				fprintf(stderr,
+					"Invalid set action mask %s/0x%x\n",
+					optarg, i);
+				return 1;
+			}
+			act_mask_tmp = i;
+			break;
+
+		case 'd':
+			if (add_devpath(optarg) != 0)
+				return 1;
+			break;
+
+		case 'I': {
+			char dev_line[256];
+			FILE *ifp = my_fopen(optarg, "r");
+
+			if (!ifp) {
+				fprintf(stderr,
+					"Invalid file for devices %s\n",
+					optarg);
+				return 1;
+			}
+
+			while (fscanf(ifp, "%s\n", dev_line) == 1) {
+				if (add_devpath(dev_line) != 0) {
+					fclose(ifp);
+					return 1;
+				}
+			}
+			fclose(ifp);
+			break;
+		}
+
+		case 'r':
+			debugfs_path = optarg;
+			break;
+
+		case 'o':
+			output_name = optarg;
+			break;
+		case 'k':
+			kill_running_trace = 1;
+			break;
+		case 'w':
+			stop_watch = atoi(optarg);
+			if (stop_watch <= 0) {
+				fprintf(stderr,
+					"Invalid stopwatch value (%d secs)\n",
+					stop_watch);
+				return 1;
+			}
+			break;
+		case 'V':
+		case 'v':
+			printf("%s version %s\n", argv[0], blktrace_version);
+			exit(0);
+			/*NOTREACHED*/
+		case 'b':
+			buf_size = strtoul(optarg, NULL, 10);
+			if (buf_size <= 0 || buf_size > 16*1024) {
+				fprintf(stderr, "Invalid buffer size (%lu)\n",
+					buf_size);
+				return 1;
+			}
+			buf_size <<= 10;
+			break;
+		case 'n':
+			buf_nr = strtoul(optarg, NULL, 10);
+			if (buf_nr <= 0) {
+				fprintf(stderr,
+					"Invalid buffer nr (%lu)\n", buf_nr);
+				return 1;
+			}
+			break;
+		case 'D':
+			output_dir = optarg;
+			break;
+		case 'h':
+			net_mode = Net_client;
+			memset(hostname, 0, sizeof(hostname));
+			strncpy(hostname, optarg, sizeof(hostname));
+			hostname[sizeof(hostname) - 1] = '\0';
+			break;
+		case 'l':
+			net_mode = Net_server;
+			break;
+		case 'p':
+			net_port = atoi(optarg);
+			break;
+		case 's':
+			net_use_sendfile = 0;
+			break;
+		default:
+			show_usage(argv[0]);
+			exit(1);
+			/*NOTREACHED*/
+		}
+	}
+
+	while (optind < argc)
+		if (add_devpath(argv[optind++]) != 0)
+			return 1;
+
+	if (net_mode != Net_server && ndevs == 0) {
+		show_usage(argv[0]);
+		return 1;
+	}
+
+	if (statfs(debugfs_path, &st) < 0) {
+		fprintf(stderr, "Invalid debug path %s: %d/%s\n",
+			debugfs_path, errno, strerror(errno));
+		return 1;
+	}
+
+	if (st.f_type != (long)DEBUGFS_TYPE) {
+		fprintf(stderr, "Debugfs is not mounted at %s\n", debugfs_path);
+		return 1;
+	}
+
+	if (act_mask_tmp != 0)
+		act_mask = act_mask_tmp;
+
+	if (net_mode == Net_client && net_setup_addr())
+		return 1;
+
+	/*
+	 * Set up for appropriate PFD handler based upon output name.
+	 */
+	if (net_client_use_sendfile())
+		handle_pfds = handle_pfds_netclient;
+	else if (net_client_use_send())
+		handle_pfds = handle_pfds_entries;
+	else if (output_name && (strcmp(output_name, "-") == 0)) {
+		piped_output = 1;
+		handle_pfds = handle_pfds_entries;
+		pfp = stdout;
+		if (setvbuf(pfp, NULL, _IONBF, 0)) {
+			perror("setvbuf stdout");
+			return 1;
+		}
+	} else
+		handle_pfds = handle_pfds_file;
+	return 0;
+}
+
+static void ch_add_connection(struct net_server_s *ns, struct cl_host *ch,
+			      int fd)
+{
+	struct cl_conn *nc;
+
+	nc = malloc(sizeof(*nc));
+	memset(nc, 0, sizeof(*nc));
+
+	time(&nc->connect_time);
+	nc->ch = ch;
+	nc->fd = fd;
+	nc->ncpus = -1;
+
+	list_add_tail(&nc->ch_head, &ch->conn_list);
+	ch->connects++;
+
+	list_add_tail(&nc->ns_head, &ns->conn_list);
+	ns->connects++;
+	ns->pfds = realloc(ns->pfds, (ns->connects+1) * sizeof(struct pollfd));
+}
+
+static void ch_rem_connection(struct net_server_s *ns, struct cl_host *ch,
+			      struct cl_conn *nc)
+{
+	net_close_connection(&nc->fd);
+
+	list_del(&nc->ch_head);
+	ch->connects--;
+
+	list_del(&nc->ns_head);
+	ns->connects--;
+	ns->pfds = realloc(ns->pfds, (ns->connects+1) * sizeof(struct pollfd));
+
+	free(nc);
+}
+
+static struct cl_host *net_find_client_host(struct net_server_s *ns,
+					    struct in_addr cl_in_addr)
+{
+	struct list_head *p;
+
+	__list_for_each(p, &ns->ch_list) {
+		struct cl_host *ch = list_entry(p, struct cl_host, head);
+
+		if (in_addr_eq(ch->cl_in_addr, cl_in_addr))
+			return ch;
+	}
+
+	return NULL;
+}
+
+static struct cl_host *net_add_client_host(struct net_server_s *ns,
+					   struct sockaddr_in *addr)
+{
+	struct cl_host *ch;
+
+	ch = malloc(sizeof(*ch));
+	memset(ch, 0, sizeof(*ch));
+
+	ch->ns = ns;
+	ch->cl_in_addr = addr->sin_addr;
+	list_add_tail(&ch->head, &ns->ch_list);
+	ns->nchs++;
+
+	ch->hostname = strdup(inet_ntoa(addr->sin_addr));
+	printf("server: connection from %s\n", ch->hostname);
+
+	INIT_LIST_HEAD(&ch->conn_list);
+	INIT_LIST_HEAD(&ch->devpaths);
+
+	return ch;
+}
+
+static void device_done(struct devpath *dpp, int ncpus)
+{
+	int cpu;
+	struct io_info *iop;
+
+	for (cpu = 0, iop = dpp->ios; cpu < ncpus; cpu++, iop++)
+		close_iop(iop);
+
+	list_del(&dpp->head);
+	dpp_free(dpp);
+}
+
+static void net_ch_remove(struct cl_host *ch, int ncpus)
+{
+	struct list_head *p, *q;
+	struct net_server_s *ns = ch->ns;
+
+	list_for_each_safe(p, q, &ch->devpaths) {
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+		device_done(dpp, ncpus);
+	}
+
+	list_for_each_safe(p, q, &ch->conn_list) {
+		struct cl_conn *nc = list_entry(p, struct cl_conn, ch_head);
+
+		ch_rem_connection(ns, ch, nc);
+	}
+
+	list_del(&ch->head);
+	ns->nchs--;
+
+	if (ch->hostname)
+		free(ch->hostname);
+	free(ch);
+}
+
+static void net_add_connection(struct net_server_s *ns)
+{
+	int fd;
+	struct cl_host *ch;
+	socklen_t socklen = sizeof(ns->addr);
+
+	fd = my_accept(ns->listen_fd, (struct sockaddr *)&ns->addr, &socklen);
+	if (fd < 0) {
+		/*
+		 * This is OK: we just won't accept this connection,
+		 * nothing fatal.
+		 */
+		perror("accept");
+	} else {
+		ch = net_find_client_host(ns, ns->addr.sin_addr);
+		if (!ch)
+			ch = net_add_client_host(ns, &ns->addr);
+
+		ch_add_connection(ns, ch, fd);
+	}
+}
+
+static struct devpath *nc_add_dpp(struct cl_conn *nc,
+				  struct blktrace_net_hdr *bnh,
+				  time_t connect_time)
+{
+	int cpu;
+	struct io_info *iop;
+	struct devpath *dpp;
+
+	dpp = malloc(sizeof(*dpp));
+	memset(dpp, 0, sizeof(*dpp));
+
+	dpp->buts_name = strdup(bnh->buts_name);
+	dpp->path = strdup(bnh->buts_name);
+	dpp->fd = -1;
+	dpp->ch = nc->ch;
+	dpp->cl_id = bnh->cl_id;
+	dpp->cl_connect_time = connect_time;
+	dpp->ncpus = nc->ncpus;
+	dpp->stats = calloc(dpp->ncpus, sizeof(*dpp->stats));
+	memset(dpp->stats, 0, dpp->ncpus * sizeof(*dpp->stats));
+
+	list_add_tail(&dpp->head, &nc->ch->devpaths);
+	nc->ch->ndevs++;
+
+	dpp->ios = calloc(nc->ncpus, sizeof(*iop));
+	memset(dpp->ios, 0, ndevs * sizeof(*iop));
+
+	for (cpu = 0, iop = dpp->ios; cpu < nc->ncpus; cpu++, iop++) {
+		iop->dpp = dpp;
+		iop->nc = nc;
+		init_mmap_info(&iop->mmap_info);
+
+		if (iop_open(iop, cpu))
+			goto err;
+	}
+
+	return dpp;
+
+err:
+	/*
+	 * Need to unravel what's been done...
+	 */
+	while (cpu >= 0)
+		close_iop(&dpp->ios[cpu--]);
+	dpp_free(dpp);
+
+	return NULL;
+}
+
+static struct devpath *nc_find_dpp(struct cl_conn *nc,
+				   struct blktrace_net_hdr *bnh)
+{
+	struct list_head *p;
+	time_t connect_time = nc->connect_time;
+
+	__list_for_each(p, &nc->ch->devpaths) {
+		struct devpath *dpp = list_entry(p, struct devpath, head);
+
+		if (!strcmp(dpp->buts_name, bnh->buts_name))
+			return dpp;
+
+		if (dpp->cl_id == bnh->cl_id)
+			connect_time = dpp->cl_connect_time;
+	}
+
+	return nc_add_dpp(nc, bnh, connect_time);
+}
+
+static void net_client_read_data(struct cl_conn *nc, struct devpath *dpp,
+				 struct blktrace_net_hdr *bnh)
+{
+	int ret;
+	struct io_info *iop = &dpp->ios[bnh->cpu];
+	struct mmap_info *mip = &iop->mmap_info;
+
+	if (setup_mmap(iop->ofd, bnh->len, &iop->mmap_info, NULL)) {
+		fprintf(stderr, "ncd(%s:%d): mmap failed\n",
+			nc->ch->hostname, nc->fd);
+		exit(1);
+	}
+
+	ret = net_recv_data(nc->fd, mip->fs_buf + mip->fs_off, bnh->len);
+	if (ret > 0) {
+		pdc_dr_update(dpp, bnh->cpu, ret);
+		mip->fs_size += ret;
+		mip->fs_off += ret;
+	} else if (ret < 0)
+		exit(1);
+}
+
+/*
+ * Returns 1 if we closed a host - invalidates other polling information
+ * that may be present.
+ */
+static int net_client_data(struct cl_conn *nc)
+{
+	int ret;
+	struct devpath *dpp;
+	struct blktrace_net_hdr bnh;
+
+	ret = net_get_header(nc, &bnh);
+	if (ret == 0)
+		return 0;
+
+	if (ret < 0) {
+		fprintf(stderr, "ncd(%d): header read failed\n", nc->fd);
+		exit(1);
+	}
+
+	if (data_is_native == -1 && check_data_endianness(bnh.magic)) {
+		fprintf(stderr, "ncd(%d): received data is bad\n", nc->fd);
+		exit(1);
+	}
+
+	if (!data_is_native) {
+		bnh.magic = be32_to_cpu(bnh.magic);
+		bnh.cpu = be32_to_cpu(bnh.cpu);
+		bnh.max_cpus = be32_to_cpu(bnh.max_cpus);
+		bnh.len = be32_to_cpu(bnh.len);
+		bnh.cl_id = be32_to_cpu(bnh.cl_id);
+		bnh.buf_size = be32_to_cpu(bnh.buf_size);
+		bnh.buf_nr = be32_to_cpu(bnh.buf_nr);
+		bnh.page_size = be32_to_cpu(bnh.page_size);
+	}
+
+	if ((bnh.magic & 0xffffff00) != BLK_IO_TRACE_MAGIC) {
+		fprintf(stderr, "ncd(%s:%d): bad data magic\n",
+			nc->ch->hostname, nc->fd);
+		exit(1);
+	}
+
+	if (nc->ncpus == -1)
+		nc->ncpus = bnh.max_cpus;
+
+	/*
+	 * len == 0 means the other end is sending us a new connection/dpp
+	 * len == 1 means that the other end signalled end-of-run
+	 */
+	dpp = nc_find_dpp(nc, &bnh);
+	if (bnh.len == 0) {
+		/*
+		 * Just adding in the dpp above is enough
+		 */
+		ack_open_close(nc->fd, dpp->buts_name);
+		nc->ch->cl_opens++;
+	} else if (bnh.len == 1) {
+		/*
+		 * overload cpu count with dropped events
+		 */
+		dpp->drops = bnh.cpu;
+
+		ack_open_close(nc->fd, dpp->buts_name);
+		if (--nc->ch->cl_opens == 0) {
+			show_stats(&nc->ch->devpaths);
+			net_ch_remove(nc->ch, nc->ncpus);
+			return 1;
+		}
+	} else
+		net_client_read_data(nc, dpp, &bnh);
+
+	return 0;
+}
+
+static void handle_client_data(struct net_server_s *ns, int events)
+{
+	struct cl_conn *nc;
+	struct pollfd *pfd;
+	struct list_head *p, *q;
+
+	pfd = &ns->pfds[1];
+	list_for_each_safe(p, q, &ns->conn_list) {
+		if (pfd->revents & POLLIN) {
+			nc = list_entry(p, struct cl_conn, ns_head);
+
+			if (net_client_data(nc) || --events == 0)
+				break;
+		}
+		pfd++;
+	}
+}
+
+static void net_setup_pfds(struct net_server_s *ns)
+{
+	struct pollfd *pfd;
+	struct list_head *p;
+
+	ns->pfds[0].fd = ns->listen_fd;
+	ns->pfds[0].events = POLLIN;
+
+	pfd = &ns->pfds[1];
+	__list_for_each(p, &ns->conn_list) {
+		struct cl_conn *nc = list_entry(p, struct cl_conn, ns_head);
+
+		pfd->fd = nc->fd;
+		pfd->events = POLLIN;
+		pfd++;
+	}
+}
+
+static int net_server_handle_connections(struct net_server_s *ns)
+{
+	int events;
+
+	printf("server: waiting for connections...\n");
+
+	while (!done) {
+		net_setup_pfds(ns);
+		events = poll(ns->pfds, ns->connects + 1, -1);
+		if (events < 0) {
+			if (errno != EINTR) {
+				perror("FATAL: poll error");
+				return 1;
+			}
+		} else if (events > 0) {
+			if (ns->pfds[0].revents & POLLIN) {
+				net_add_connection(ns);
+				events--;
+			}
+
+			if (events)
+				handle_client_data(ns, events);
+		}
+	}
+
+	return 0;
+}
+
+static int net_server(void)
+{
+	int fd, opt;
+	int ret = 1;
+	struct net_server_s net_server;
+	struct net_server_s *ns = &net_server;
+
+	memset(ns, 0, sizeof(*ns));
+	INIT_LIST_HEAD(&ns->ch_list);
+	INIT_LIST_HEAD(&ns->conn_list);
+	ns->pfds = malloc(sizeof(struct pollfd));
+
+	fd = my_socket(AF_INET, SOCK_STREAM, 0);
+	if (fd < 0) {
+		perror("server: socket");
+		goto out;
+	}
+
+	opt = 1;
+	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
+		perror("setsockopt");
+		goto out;
+	}
+
+	memset(&ns->addr, 0, sizeof(ns->addr));
+	ns->addr.sin_family = AF_INET;
+	ns->addr.sin_addr.s_addr = htonl(INADDR_ANY);
+	ns->addr.sin_port = htons(net_port);
+
+	if (bind(fd, (struct sockaddr *) &ns->addr, sizeof(ns->addr)) < 0) {
+		perror("bind");
+		goto out;
+	}
+
+	if (listen(fd, 1) < 0) {
+		perror("listen");
+		goto out;
+	}
+
+	/*
+	 * The actual server looping is done here:
+	 */
+	ns->listen_fd = fd;
+	ret = net_server_handle_connections(ns);
+
+	/*
+	 * Clean up and return...
+	 */
+out:
+	free(ns->pfds);
+	return ret;
+}
+
+static int run_tracers(void)
+{
+	atexit(exit_tracing);
+	if (net_mode == Net_client)
+		printf("blktrace: connecting to %s\n", hostname);
+
+	setup_buts();
+
+	if (use_tracer_devpaths()) {
+		if (setup_tracer_devpaths())
+			return 1;
+
+		if (piped_output)
+			handle_list = handle_list_file;
+		else
+			handle_list = handle_list_net;
+	}
+
+	start_tracers();
+	if (nthreads_running == ncpus) {
+		unblock_tracers();
+		start_buts();
+		if (net_mode == Net_client)
+			printf("blktrace: connected!\n");
+		if (stop_watch)
+			alarm(stop_watch);
+	} else
+		stop_tracers();
+
+	wait_tracers();
+	if (nthreads_running == ncpus)
+		show_stats(&devpaths);
+	if (net_client_use_send())
+		close_client_connections();
+	del_tracers();
+
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	int ret = 0;
+
+	setlocale(LC_NUMERIC, "en_US");
+	pagesize = getpagesize();
+	ncpus = sysconf(_SC_NPROCESSORS_CONF);
+	if (ncpus < 0) {
+		fprintf(stderr, "sysconf(_SC_NPROCESSORS_CONF) failed %d/%s\n",
+			errno, strerror(errno));
+		ret = 1;
+		goto out;
+	} else if (handle_args(argc, argv)) {
+		ret = 1;
+		goto out;
+	}
+
+	if (ndevs > 1 && output_name && strcmp(output_name, "-") != 0) {
+		fprintf(stderr, "-o not supported with multiple devices\n");
+		ret = 1;
+		goto out;
+	}
+
+	signal(SIGINT, handle_sigint);
+	signal(SIGHUP, handle_sigint);
+	signal(SIGTERM, handle_sigint);
+	signal(SIGALRM, handle_sigint);
+	signal(SIGPIPE, SIG_IGN);
+
+	if (kill_running_trace) {
+		struct devpath *dpp;
+		struct list_head *p;
+
+		__list_for_each(p, &devpaths) {
+			dpp = list_entry(p, struct devpath, head);
+			if (__stop_trace(dpp->fd)) {
+				fprintf(stderr,
+					"BLKTRACETEARDOWN %s failed: %d/%s\n",
+					dpp->path, errno, strerror(errno));
+			}
+		}
+	} else if (net_mode == Net_server) {
+		if (output_name) {
+			fprintf(stderr, "-o ignored in server mode\n");
+			output_name = NULL;
+		}
+		ret = net_server();
+	} else
+		ret = run_tracers();
+
+out:
+	if (pfp)
+		fclose(pfp);
+	rel_devpaths();
+	return ret;
+}
diff --git a/blktrace.h b/blktrace.h
new file mode 100644
index 0000000..380aec7
--- /dev/null
+++ b/blktrace.h
@@ -0,0 +1,150 @@
+#ifndef BLKTRACE_H
+#define BLKTRACE_H
+
+#include <stdio.h>
+#include <limits.h>
+#include <byteswap.h>
+#include <endian.h>
+
+#include "blktrace_api.h"
+#include "rbtree.h"
+
+#define MINORBITS	20
+#define MINORMASK	((1U << MINORBITS) - 1)
+#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
+#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
+
+#define SECONDS(x) 		((unsigned long long)(x) / 1000000000)
+#define NANO_SECONDS(x)		((unsigned long long)(x) % 1000000000)
+#define DOUBLE_TO_NANO_ULL(d)	((unsigned long long)((d) * 1000000000))
+
+#define min(a, b)	((a) < (b) ? (a) : (b))
+#define max(a, b)	((a) > (b) ? (a) : (b))
+
+#define t_sec(t)	((t)->bytes >> 9)
+#define t_kb(t)		((t)->bytes >> 10)
+#define t_b(t)		((t)->bytes & 1023)
+
+typedef __u32 u32;
+typedef __u8 u8;
+
+struct io_stats {
+	unsigned long qreads, qwrites, creads, cwrites, mreads, mwrites;
+	unsigned long ireads, iwrites, rrqueue, wrqueue;
+	unsigned long long qread_kb, qwrite_kb, cread_kb, cwrite_kb;
+	unsigned long long qread_b, qwrite_b, cread_b, cwrite_b;
+	unsigned long long iread_kb, iwrite_kb;
+	unsigned long long mread_kb, mwrite_kb;
+	unsigned long long mread_b, mwrite_b, iread_b, iwrite_b;
+	unsigned long qreads_pc, qwrites_pc, ireads_pc, iwrites_pc;
+	unsigned long rrqueue_pc, wrqueue_pc, creads_pc, cwrites_pc;
+	unsigned long long qread_kb_pc, qwrite_kb_pc, iread_kb_pc, iwrite_kb_pc;
+	unsigned long long qread_b_pc, qwrite_b_pc, iread_b_pc, iwrite_b_pc;
+	unsigned long io_unplugs, timer_unplugs;
+};
+
+struct per_cpu_info {
+	unsigned int cpu;
+	unsigned int nelems;
+
+	int fd;
+	int fdblock;
+	char fname[PATH_MAX];
+
+	struct io_stats io_stats;
+
+	struct rb_root rb_last;
+	unsigned long rb_last_entries;
+	unsigned long last_sequence;
+	unsigned long smallest_seq_read;
+
+	struct skip_info *skips_head;
+	struct skip_info *skips_tail;
+};
+
+extern FILE *ofp;
+extern int data_is_native;
+extern struct timespec abs_start_time;
+
+#define CHECK_MAGIC(t)		(((t)->magic & 0xffffff00) == BLK_IO_TRACE_MAGIC)
+#define SUPPORTED_VERSION	(0x07)
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define be16_to_cpu(x)		__bswap_16(x)
+#define be32_to_cpu(x)		__bswap_32(x)
+#define be64_to_cpu(x)		__bswap_64(x)
+#define cpu_to_be16(x)		__bswap_16(x)
+#define cpu_to_be32(x)		__bswap_32(x)
+#define cpu_to_be64(x)		__bswap_64(x)
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define be16_to_cpu(x)		(x)
+#define be32_to_cpu(x)		(x)
+#define be64_to_cpu(x)		(x)
+#define cpu_to_be16(x)		(x)
+#define cpu_to_be32(x)		(x)
+#define cpu_to_be64(x)		(x)
+#else
+#error "Bad arch"
+#endif
+
+static inline int verify_trace(struct blk_io_trace *t)
+{
+	if (!CHECK_MAGIC(t)) {
+		fprintf(stderr, "bad trace magic %x\n", t->magic);
+		return 1;
+	}
+	if ((t->magic & 0xff) != SUPPORTED_VERSION) {
+		fprintf(stderr, "unsupported trace version %x\n", 
+			t->magic & 0xff);
+		return 1;
+	}
+
+	return 0;
+}
+
+static inline void trace_to_cpu(struct blk_io_trace *t)
+{
+	if (data_is_native)
+		return;
+
+	t->magic	= be32_to_cpu(t->magic);
+	t->sequence	= be32_to_cpu(t->sequence);
+	t->time		= be64_to_cpu(t->time);
+	t->sector	= be64_to_cpu(t->sector);
+	t->bytes	= be32_to_cpu(t->bytes);
+	t->action	= be32_to_cpu(t->action);
+	t->pid		= be32_to_cpu(t->pid);
+	t->device	= be32_to_cpu(t->device);
+	t->cpu		= be32_to_cpu(t->cpu);
+	t->error	= be16_to_cpu(t->error);
+	t->pdu_len	= be16_to_cpu(t->pdu_len);
+}
+
+/*
+ * check whether data is native or not
+ */
+static inline int check_data_endianness(u32 magic)
+{
+	if ((magic & 0xffffff00) == BLK_IO_TRACE_MAGIC) {
+		data_is_native = 1;
+		return 0;
+	}
+
+	magic = __bswap_32(magic);
+	if ((magic & 0xffffff00) == BLK_IO_TRACE_MAGIC) {
+		data_is_native = 0;
+		return 0;
+	}
+
+	return 1;
+}
+
+extern void set_all_format_specs(char *);
+extern int add_format_spec(char *);
+extern void process_fmt(char *, struct per_cpu_info *, struct blk_io_trace *,
+			unsigned long long, int, unsigned char *);
+extern int valid_act_opt(int);
+extern int find_mask_map(char *);
+extern char *find_process_name(pid_t);
+
+#endif
diff --git a/blktrace_api.h b/blktrace_api.h
new file mode 100644
index 0000000..b222218
--- /dev/null
+++ b/blktrace_api.h
@@ -0,0 +1,137 @@
+#ifndef BLKTRACEAPI_H
+#define BLKTRACEAPI_H
+
+#include <asm/types.h>
+
+/*
+ * Trace categories
+ */
+enum {
+	BLK_TC_READ	= 1 << 0,	/* reads */
+	BLK_TC_WRITE	= 1 << 1,	/* writes */
+	BLK_TC_FLUSH	= 1 << 2,	/* flush */
+	BLK_TC_SYNC	= 1 << 3,	/* sync */
+	BLK_TC_QUEUE	= 1 << 4,	/* queueing/merging */
+	BLK_TC_REQUEUE	= 1 << 5,	/* requeueing */
+	BLK_TC_ISSUE	= 1 << 6,	/* issue */
+	BLK_TC_COMPLETE	= 1 << 7,	/* completions */
+	BLK_TC_FS	= 1 << 8,	/* fs requests */
+	BLK_TC_PC	= 1 << 9,	/* pc requests */
+	BLK_TC_NOTIFY	= 1 << 10,	/* special message */
+	BLK_TC_AHEAD	= 1 << 11,	/* readahead */
+	BLK_TC_META	= 1 << 12,	/* metadata */
+	BLK_TC_DISCARD	= 1 << 13,	/* discard requests */
+	BLK_TC_DRV_DATA	= 1 << 14,	/* binary driver data */
+	BLK_TC_FUA	= 1 << 15,	/* fua requests */
+
+	BLK_TC_END	= 1 << 15,	/* we've run out of bits! */
+};
+
+#define BLK_TC_SHIFT		(16)
+#define BLK_TC_ACT(act)		((act) << BLK_TC_SHIFT)
+
+/*
+ * Basic trace actions
+ */
+enum {
+	__BLK_TA_QUEUE = 1,		/* queued */
+	__BLK_TA_BACKMERGE,		/* back merged to existing rq */
+	__BLK_TA_FRONTMERGE,		/* front merge to existing rq */
+	__BLK_TA_GETRQ,			/* allocated new request */
+	__BLK_TA_SLEEPRQ,		/* sleeping on rq allocation */
+	__BLK_TA_REQUEUE,		/* request requeued */
+	__BLK_TA_ISSUE,			/* sent to driver */
+	__BLK_TA_COMPLETE,		/* completed by driver */
+	__BLK_TA_PLUG,			/* queue was plugged */
+	__BLK_TA_UNPLUG_IO,		/* queue was unplugged by io */
+	__BLK_TA_UNPLUG_TIMER,		/* queue was unplugged by timer */
+	__BLK_TA_INSERT,		/* insert request */
+	__BLK_TA_SPLIT,			/* bio was split */
+	__BLK_TA_BOUNCE,		/* bio was bounced */
+	__BLK_TA_REMAP,			/* bio was remapped */
+	__BLK_TA_ABORT,			/* request aborted */
+	__BLK_TA_DRV_DATA,		/* binary driver data */
+};
+
+/*
+ * Notify events.
+ */
+enum blktrace_notify {
+	__BLK_TN_PROCESS = 0,		/* establish pid/name mapping */
+	__BLK_TN_TIMESTAMP,		/* include system clock */
+	__BLK_TN_MESSAGE,               /* Character string message */
+};
+
+/*
+ * Trace actions in full. Additionally, read or write is masked
+ */
+#define BLK_TA_QUEUE		(__BLK_TA_QUEUE | BLK_TC_ACT(BLK_TC_QUEUE))
+#define BLK_TA_BACKMERGE	(__BLK_TA_BACKMERGE | BLK_TC_ACT(BLK_TC_QUEUE))
+#define BLK_TA_FRONTMERGE	(__BLK_TA_FRONTMERGE | BLK_TC_ACT(BLK_TC_QUEUE))
+#define	BLK_TA_GETRQ		(__BLK_TA_GETRQ | BLK_TC_ACT(BLK_TC_QUEUE))
+#define	BLK_TA_SLEEPRQ		(__BLK_TA_SLEEPRQ | BLK_TC_ACT(BLK_TC_QUEUE))
+#define	BLK_TA_REQUEUE		(__BLK_TA_REQUEUE | BLK_TC_ACT(BLK_TC_REQUEUE))
+#define BLK_TA_ISSUE		(__BLK_TA_ISSUE | BLK_TC_ACT(BLK_TC_ISSUE))
+#define BLK_TA_COMPLETE		(__BLK_TA_COMPLETE| BLK_TC_ACT(BLK_TC_COMPLETE))
+#define BLK_TA_PLUG		(__BLK_TA_PLUG | BLK_TC_ACT(BLK_TC_QUEUE))
+#define BLK_TA_UNPLUG_IO	(__BLK_TA_UNPLUG_IO | BLK_TC_ACT(BLK_TC_QUEUE))
+#define BLK_TA_UNPLUG_TIMER	(__BLK_TA_UNPLUG_TIMER | BLK_TC_ACT(BLK_TC_QUEUE))
+#define BLK_TA_INSERT		(__BLK_TA_INSERT | BLK_TC_ACT(BLK_TC_QUEUE))
+#define BLK_TA_SPLIT		(__BLK_TA_SPLIT)
+#define BLK_TA_BOUNCE		(__BLK_TA_BOUNCE)
+#define BLK_TA_REMAP		(__BLK_TA_REMAP | BLK_TC_ACT(BLK_TC_QUEUE))
+#define BLK_TA_ABORT		(__BLK_TA_ABORT | BLK_TC_ACT(BLK_TC_QUEUE))
+#define BLK_TA_DRV_DATA		(__BLK_TA_DRV_DATA | BLK_TC_ACT(BLK_TC_DRV_DATA))
+
+#define BLK_TN_PROCESS		(__BLK_TN_PROCESS | BLK_TC_ACT(BLK_TC_NOTIFY))
+#define BLK_TN_TIMESTAMP	(__BLK_TN_TIMESTAMP | BLK_TC_ACT(BLK_TC_NOTIFY))
+#define BLK_TN_MESSAGE		(__BLK_TN_MESSAGE | BLK_TC_ACT(BLK_TC_NOTIFY))
+
+#define BLK_IO_TRACE_MAGIC	0x65617400
+#define BLK_IO_TRACE_VERSION	0x07
+
+/*
+ * The trace itself
+ */
+struct blk_io_trace {
+	__u32 magic;		/* MAGIC << 8 | version */
+	__u32 sequence;		/* event number */
+	__u64 time;		/* in nanoseconds */
+	__u64 sector;		/* disk offset */
+	__u32 bytes;		/* transfer length */
+	__u32 action;		/* what happened */
+	__u32 pid;		/* who did it */
+	__u32 device;		/* device identifier (dev_t) */
+	__u32 cpu;		/* on what cpu did it happen */
+	__u16 error;		/* completion error */
+	__u16 pdu_len;		/* length of data after this trace */
+};
+
+/*
+ * The remap event
+ */
+struct blk_io_trace_remap {
+	__u32 device_from;
+	__u32 device_to;
+	__u64 sector_from;
+};
+
+/*
+ * User setup structure passed with BLKSTARTTRACE
+ */
+struct blk_user_trace_setup {
+	char name[32];			/* output */
+	__u16 act_mask;			/* input */
+	__u32 buf_size;			/* input */
+	__u32 buf_nr;			/* input */
+	__u64 start_lba;
+	__u64 end_lba;
+	__u32 pid;
+};
+
+#define BLKTRACESETUP _IOWR(0x12,115,struct blk_user_trace_setup)
+#define BLKTRACESTART _IO(0x12,116)
+#define BLKTRACESTOP _IO(0x12,117)
+#define BLKTRACETEARDOWN _IO(0x12,118)
+
+#endif
diff --git a/btrace b/btrace
new file mode 100644
index 0000000..45c10cf
--- /dev/null
+++ b/btrace
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Copyright (c) 2005 Silicon Graphics, Inc.
+# All rights reserved.
+# 
+#	Nathan Scott <nathans@sgi.com>
+#	14 Sep 2005	Initial version
+# 
+
+TRACEOPTS=""
+PARSEOPTS="-b100000"
+USAGE="Usage: btrace [-s] [-t] [-w N] [-n N] [-b N] [-a <trace>...] [-r <dbg mnt>] <dev>..."
+DIRNAME=`dirname $0`
+
+while getopts "a:w:n:b:r:sthv" c
+do
+	case $c in
+	a)	TRACEOPTS=$TRACEOPTS" -a "$OPTARG" ";;
+	w)	TRACEOPTS=$TRACEOPTS" -w "$OPTARG" ";;
+	n)	TRACEOPTS=$TRACEOPTS" -n "$OPTARG" ";;
+	b)	TRACEOPTS=$TRACEOPTS" -b "$OPTARG" ";;
+	r)	TRACEOPTS=$TRACEOPTS" -r "$OPTARG" ";;
+	s)	PARSEOPTS=$PARSEOPTS" -s";;
+	t)	PARSEOPTS=$PARSEOPTS" -t";;
+	h)	PARSEOPTS=$PARSEOPTS" -h";;
+	v)	PARSEOPTS=$PARSEOPTS" -v";;
+	\?)	echo $USAGE 1>&2
+		exit 2
+		;;
+	esac
+done
+
+shift `expr $OPTIND - 1`
+if [ $# -eq 0 ]; then
+	echo $USAGE 1>&2
+	exit 2
+fi
+
+${DIRNAME}/blktrace ${TRACEOPTS} -o- $@ | ${DIRNAME}/blkparse ${PARSEOPTS} -i-
diff --git a/btrace.spec b/btrace.spec
new file mode 100644
index 0000000..90f88d8
--- /dev/null
+++ b/btrace.spec
@@ -0,0 +1,55 @@
+#
+# spec file for package btrace (Version 1.0)
+#
+# Copyright (c) 2005 SUSE LINUX Products GmbH, Nuernberg, Germany.
+# This file and all modifications and additions to the pristine
+# package are under the same license as the package itself.
+#
+# Please submit bugfixes or comments via http://www.suse.de/feedback/
+#
+
+# norootforbuild
+# neededforbuild tetex te_latex gcc
+
+Name:         btrace
+License:      GPL
+Group:        foo
+Version:      1.0
+Release:      1
+URL:          http://brick.kernel.dk/snaps
+Summary:      Block IO tracer
+Source0:      %name-%version.tar.bz2
+BuildRoot:    %{_tmppath}/%{name}-%{version}-build
+
+%description
+btrace can show detailed info about what is happening on a block
+device io queue. This is valuable for diagnosing and fixing
+performance or application problems relating to block layer io.
+
+
+Authors:
+--------
+    Jens Axboe <axboe@kernel.dk>
+
+%prep
+%setup -q
+
+%build
+make CFLAGS="$RPM_OPT_FLAGS" all docs
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make dest=$RPM_BUILD_ROOT prefix=$RPM_BUILD_ROOT/%{_prefix} install
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root)
+%doc README doc/blktrace.pdf
+/usr/bin/*
+/usr/man/*
+
+%changelog -n btrace
+* Mon Oct 10 2005 - axboe@suse.de
+- Initial version
diff --git a/btreplay/Makefile b/btreplay/Makefile
new file mode 100644
index 0000000..2998182
--- /dev/null
+++ b/btreplay/Makefile
@@ -0,0 +1,45 @@
+#
+# OCFLAGS:
+# 	COUNT_IOS	- Counts struct io's left at end
+# 	DEBUG		- Various and sundy debug asserts
+# 	NDEBUG		- Defined: no asserts, Undefined: asserts
+#
+
+CC	= gcc
+CFLAGS	= -Wall -W -O2 -g
+INCS	= -I. -I.. -I../btt
+OCFLAGS	= -UCOUNT_IOS -UDEBUG -DNDEBUG
+XCFLAGS	= -D_GNU_SOURCE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
+override CFLAGS += $(INCS) $(XCFLAGS) $(OCFLAGS)
+
+PROGS	= btrecord btreplay
+LIBS	= -laio -lrt -lpthread
+
+all: depend $(PROGS)
+
+$(PROGS): | depend
+
+docs:
+	$(MAKE) -C doc all
+
+docsclean:
+	$(MAKE) -C doc clean
+
+clean: docsclean
+	-rm -f *.o $(PROGS) .depend
+
+%.o: %.c
+	$(CC) $(CFLAGS) -c -o $*.o $<
+
+btrecord: btrecord.o
+	$(CC) $(CFLAGS) -o $@ $(filter %.o,$^)
+
+btreplay: btreplay.o
+	$(CC) $(CFLAGS) -o $@ $(filter %.o,$^) $(LIBS)
+
+depend:
+	@$(CC) -MM $(CFLAGS) *.c 1> .depend
+
+ifneq ($(wildcard .depend),)
+include .depend
+endif
diff --git a/btreplay/btrecord.c b/btreplay/btrecord.c
new file mode 100644
index 0000000..3646257
--- /dev/null
+++ b/btreplay/btrecord.c
@@ -0,0 +1,789 @@
+/*
+ * Blktrace record utility - Convert binary trace data into bunches of IOs
+ *
+ * Copyright (C) 2007 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ */
+
+static char build_date[] = __DATE__ " at "__TIME__;
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <stdarg.h>
+
+#if !defined(_GNU_SOURCE)
+#	define _GNU_SOURCE
+#endif
+#include <getopt.h>
+
+#include "list.h"
+#include "btrecord.h"
+#include "blktrace.h"
+
+/*
+ * Per input file information
+ *
+ * @head: 	Used to link up on input_files
+ * @devnm: 	Device name portion of this input file
+ * @file_name: 	Fully qualified name for this input file
+ * @cpu: 	CPU that this file was collected on
+ * @ifd: 	Input file descriptor (when opened)
+ * @tpkts: 	Total number of packets processed.
+ */
+struct ifile_info {
+	struct list_head head;
+	char *devnm, *file_name;
+	int cpu, ifd;
+	__u64 tpkts, genesis;
+};
+
+/*
+ * Per IO trace information
+ *
+ * @time: 	Time stamp when trace was emitted
+ * @sector: 	IO sector identifier
+ * @bytes: 	Number of bytes transferred
+ * @rw: 	Read (1) or write (0) 
+ */
+struct io_spec {
+	__u64 time;
+	__u64 sector;
+	__u32 bytes;
+	int rw;
+};
+
+/*
+ * Per output file information
+ *
+ * @ofp: 	Output file 
+ * @vfp:	Verbose output file
+ * @file_name: 	Fully qualified name for this file
+ * @vfn:	Fully qualified name for this file
+ * @cur: 	Current IO bunch being collected
+ * @iip: 	Input file this is associated with
+ * @start_time: Start time of th ecurrent bunch
+ * @last_time: 	Time of last packet put in
+ * @bunches: 	Number of bunches processed
+ * @pkts: 	Number of packets stored in bunches
+ */
+struct io_stream {
+	FILE *ofp, *vfp;
+	char *file_name, *vfn;
+	struct io_bunch *cur;
+	struct ifile_info *iip;
+	__u64 start_time, last_time, bunches, pkts;
+};
+
+int data_is_native;				// Indicates whether to swap
+static LIST_HEAD(input_files);			// List of all input files
+static char *idir = ".";			// Input directory base
+static char *odir = ".";			// Output directory base
+static char *obase = "replay";			// Output file base
+static __u64 max_bunch_tm = (10 * 1000 * 1000);	// 10 milliseconds
+static __u64 max_pkts_per_bunch = 8;		// Default # of pkts per bunch
+static int verbose = 0;				// Boolean: output stats
+static int find_traces = 0;			// Boolean: Find traces in dir
+
+static char usage_str[] =                                                  \
+        "\n"                                                               \
+	"\t[ -d <dir>  : --input-directory=<dir> ] Default: .\n"           \
+	"\t[ -D <dir>  : --output-directory=<dir>] Default: .\n"           \
+	"\t[ -F        : --find-traces           ] Default: Off\n"         \
+        "\t[ -h        : --help                  ] Default: Off\n"         \
+        "\t[ -m <nsec> : --max-bunch-time=<nsec> ] Default: 10 msec\n"     \
+	"\t[ -M <pkts> : --max-pkts=<pkts>       ] Default: 8\n"           \
+        "\t[ -o <base> : --output-base=<base>    ] Default: replay\n"      \
+        "\t[ -v        : --verbose               ] Default: Off\n"         \
+        "\t[ -V        : --version               ] Default: Off\n"         \
+	"\t<dev>...                                Default: None\n"	   \
+        "\n";
+
+#define S_OPTS	"d:D:Fhm:M:o:vV"
+static struct option l_opts[] = {
+	{
+		.name = "input-directory",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'd'
+	},
+	{
+		.name = "output-directory",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'D'
+	},
+	{
+		.name = "find-traces",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'F'
+	},
+	{
+		.name = "help",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'h'
+	},
+	{
+		.name = "max-bunch-time",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'm'
+	},
+	{
+		.name = "max-pkts",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'M'
+	},
+	{
+		.name = "output-base",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'o'
+	},
+	{
+		.name = "verbose",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'v'
+	},
+	{
+		.name = "version",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'V'
+	},
+	{
+		.name = NULL
+	}
+};
+
+#define ERR_ARGS			1
+#define ERR_SYSCALL			2
+static inline void fatal(const char *errstring, const int exitval,
+			 const char *fmt, ...)
+{
+	va_list ap;
+
+	if (errstring)
+		perror(errstring);
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+
+	exit(exitval);
+	/*NOTREACHED*/
+}
+
+/**
+ * match - Return true if this trace is a proper QUEUE transaction
+ * @action: Action field from trace
+ */
+static inline int match(__u32 action)
+{
+	return ((action & 0xffff) == __BLK_TA_QUEUE) &&
+				       (action & BLK_TC_ACT(BLK_TC_QUEUE));
+}
+
+/**
+ * usage - Display usage string and version
+ */
+static void usage(void)
+{
+	fprintf(stderr, "Usage: btrecord -- version %s\n%s", 
+		my_btversion, usage_str);
+}
+
+/**
+ * write_file_hdr - Seek to and write btrecord file header
+ * @stream: Output file information
+ * @hdr: Header to write
+ */
+static void write_file_hdr(struct io_stream *stream, struct io_file_hdr *hdr)
+{
+	hdr->version = mk_btversion(btver_mjr, btver_mnr, btver_sub);
+
+	if (verbose) {
+		fprintf(stderr, "\t%s: %llx %llx %llx %llx\n", 
+			stream->file_name,
+			(long long unsigned)hdr->version,
+			(long long unsigned)hdr->genesis,
+			(long long unsigned)hdr->nbunches,
+			(long long unsigned)hdr->total_pkts);
+	}
+
+	fseek(stream->ofp, 0, SEEK_SET);
+	if (fwrite(hdr, sizeof(*hdr), 1, stream->ofp) != 1) {
+		fatal(stream->file_name, ERR_SYSCALL, "Hdr write failed\n");
+		/*NOTREACHED*/
+	}
+}
+
+/**
+ * io_bunch_create - Allocate & initialize an io_bunch
+ * @io_stream: IO stream being added to
+ * @pre_stall: Amount of time that this bunch should be delayed by
+ * @start_time: Records current start 
+ */
+static inline void io_bunch_create(struct io_stream *stream, __u64 start_time)
+{
+	struct io_bunch *cur = malloc(sizeof(*cur));
+
+	memset(cur, 0, sizeof(*cur));
+
+	cur->hdr.npkts = 0;
+	cur->hdr.time_stamp = stream->start_time = start_time;
+
+	stream->cur = cur;
+}
+
+/**
+ * io_bunch_add - Add an IO to the current bunch of IOs
+ * @stream: Per-output file stream information
+ * @spec: IO trace specification
+ *
+ * Returns update bunch information
+ */
+static void io_bunch_add(struct io_stream *stream, struct io_spec *spec)
+{
+	struct io_bunch *cur = stream->cur;
+	struct io_pkt iop = {
+		.sector = spec->sector,
+		.nbytes = spec->bytes,
+		.rw = spec->rw
+	};
+
+	assert(cur != NULL);
+	assert(cur->hdr.npkts < BT_MAX_PKTS);
+	assert(stream->last_time == 0 || stream->last_time <= spec->time);
+
+	cur->pkts[cur->hdr.npkts++] = iop;	// Struct copy
+	stream->last_time = spec->time;
+}
+
+/**
+ * rem_input_file - Release resources associated with an input file
+ * @iip: Per-input file information
+ */
+static void rem_input_file(struct ifile_info *iip)
+{
+	list_del(&iip->head);
+
+	close(iip->ifd);
+	free(iip->file_name);
+	free(iip->devnm);
+	free(iip);
+}
+
+/**
+ * __add_input_file - Allocate and initialize per-input file structure
+ * @cpu: CPU for this file
+ * @devnm: Device name for this file
+ * @file_name: Fully qualifed input file name
+ */
+static void __add_input_file(int cpu, char *devnm, char *file_name)
+{
+	struct ifile_info *iip = malloc(sizeof(*iip));
+
+	iip->cpu = cpu;
+	iip->tpkts = 0;
+	iip->genesis = 0;
+	iip->devnm = strdup(devnm);
+	iip->file_name = strdup(file_name);
+	iip->ifd = open(file_name, O_RDONLY);
+	if (iip->ifd < 0) {
+		fatal(file_name, ERR_ARGS, "Unable to open\n");
+		/*NOTREACHED*/
+	}
+
+	list_add_tail(&iip->head, &input_files);
+}
+
+/**
+ * add_input_file - Set up the input file name
+ * @devnm: Device name to use
+ */
+static void add_input_file(char *devnm)
+{
+	struct list_head *p;
+	int cpu, found = 0;
+
+	__list_for_each(p, &input_files) {
+		struct ifile_info *iip = list_entry(p, struct ifile_info, head);
+		if (strcmp(iip->devnm, devnm) == 0)
+			return;
+	}
+
+	for (cpu = 0; ; cpu++) {
+		char full_name[MAXPATHLEN];
+
+		sprintf(full_name, "%s/%s.blktrace.%d", idir, devnm, cpu);
+		if (access(full_name, R_OK) != 0)
+			break;
+
+		__add_input_file(cpu, devnm, full_name);
+		found++;
+	}
+
+	if (!found) {
+		fatal(NULL, ERR_ARGS, "No traces found for %s\n", devnm);
+		/*NOTREACHED*/
+	}
+}
+
+static void find_input_files(char *idir)
+{
+	struct dirent *ent;
+	DIR *dir = opendir(idir);
+
+	if (dir == NULL) {
+		fatal(idir, ERR_ARGS, "Unable to open %s\n", idir);
+		/*NOTREACHED*/
+	}
+
+	while ((ent = readdir(dir)) != NULL) {
+		char *p, *dsf;
+
+		if (strstr(ent->d_name, ".blktrace.") == NULL)
+			continue;
+
+		dsf = strdup(ent->d_name);
+		p = index(dsf, '.');
+		assert(p != NULL);
+		*p = '\0';
+		add_input_file(dsf);
+		free(dsf);
+	}
+
+	closedir(dir);
+}
+
+/**
+ * handle_args - Parse passed in argument list
+ * @argc: Number of arguments in argv
+ * @argv: Arguments passed in
+ *
+ * Does rudimentary parameter verification as well.
+ */
+void handle_args(int argc, char *argv[])
+{
+	int c;
+
+	while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
+		switch (c) {
+		case 'd':
+			idir = optarg;
+			if (access(idir, R_OK | X_OK) != 0) {
+				fatal(idir, ERR_ARGS, 
+				      "Invalid input directory specified\n");
+				/*NOTREACHED*/
+			}
+			break;
+
+		case 'D':
+			odir = optarg;
+			if (access(odir, R_OK | X_OK) != 0) {
+				fatal(odir, ERR_ARGS, 
+				      "Invalid output directory specified\n");
+				/*NOTREACHED*/
+			}
+			break;
+
+		case 'F': 
+			find_traces = 1;
+			break;
+
+		case 'h': 
+			usage(); 
+			exit(0);
+			/*NOTREACHED*/
+
+		case 'm':
+			max_bunch_tm = (__u64)atoll(optarg);
+			if (max_bunch_tm < 1) {
+				fprintf(stderr, "Invalid bunch time %llu\n",
+					(unsigned long long)max_bunch_tm);
+				exit(ERR_ARGS);
+				/*NOTREACHED*/
+			}
+			break;
+
+		case 'M':
+			max_pkts_per_bunch = (__u64)atoll(optarg);
+			if (!((1 <= max_pkts_per_bunch) && 
+						(max_pkts_per_bunch < 513))) {
+				fprintf(stderr, "Invalid max pkts %llu\n",
+					(unsigned long long)max_pkts_per_bunch);
+				exit(ERR_ARGS);
+				/*NOTREACHED*/
+			}
+			break;
+
+		case 'o':
+			obase = optarg;
+			break;
+
+		case 'V':
+			fprintf(stderr, "btrecord -- version %s\n", 
+				my_btversion);
+			fprintf(stderr, "            Built on %s\n", build_date);
+			exit(0);
+			/*NOTREACHED*/
+
+		case 'v':
+			verbose++;
+			break;
+
+		default:
+			usage();
+			fatal(NULL, ERR_ARGS, "Invalid command line\n");
+			/*NOTREACHED*/
+		}
+	}
+
+	while (optind < argc)
+		add_input_file(argv[optind++]);
+
+	if (find_traces)
+		find_input_files(idir);
+
+	if (list_len(&input_files) == 0) {
+		fatal(NULL, ERR_ARGS, "Missing required input file name(s)\n");
+		/*NOTREACHED*/
+	}
+}
+
+/**
+ * next_io - Retrieve next Q trace from input stream
+ * @iip: Per-input file information
+ * @spec: IO specifier for trace
+ *
+ * Returns 0 on end of file, 1 if valid data returned.
+ */
+static int next_io(struct ifile_info *iip, struct io_spec *spec)
+{
+	ssize_t ret;
+	__u32 action;
+	__u16 pdu_len;
+	struct blk_io_trace t;
+
+again:
+	ret = read(iip->ifd, &t, sizeof(t));
+	if (ret < 0) {
+		fatal(iip->file_name, ERR_SYSCALL, "Read failed\n");
+		/*NOTREACHED*/
+	}
+	else if (ret == 0)
+		return 0;
+	else if (ret < (ssize_t)sizeof(t)) {
+		fprintf(stderr, "WARNING: Short read on %s (%d)\n", 
+			iip->file_name, (int)ret);
+		return 0;
+	}
+
+	if (data_is_native == -1)
+		check_data_endianness(t.magic);
+
+	assert(data_is_native >= 0);
+	if (data_is_native) {
+		spec->time = t.time;
+		spec->sector = t.sector;
+		spec->bytes = t.bytes;
+		action = t.action;
+		pdu_len = t.pdu_len;
+	}
+	else {
+		spec->time = be64_to_cpu(t.time);
+		spec->sector = be64_to_cpu(t.sector);
+		spec->bytes = be32_to_cpu(t.bytes);
+		action = be32_to_cpu(t.action);
+		pdu_len = be16_to_cpu(t.pdu_len);
+	}
+
+
+	if (pdu_len) {
+		char buf[pdu_len];
+
+		ret = read(iip->ifd, buf, pdu_len);
+		if (ret < 0) {
+			fatal(iip->file_name, ERR_SYSCALL, "Read PDU failed\n");
+			/*NOTREACHED*/
+		}
+		else if (ret < (ssize_t)pdu_len) {
+			fprintf(stderr, "WARNING: Short PDU read on %s (%d)\n", 
+				iip->file_name, (int)ret);
+			return 0;
+		}
+	}
+
+	iip->tpkts++;
+	if (!match(action))
+		goto again;
+
+	spec->rw = (action & BLK_TC_ACT(BLK_TC_READ)) ? 1 : 0;
+	if (verbose > 1)
+		fprintf(stderr, "%2d: %10llu+%10llu (%d) @ %10llx\n",
+			iip->cpu, (long long unsigned)spec->sector,
+			(long long unsigned)spec->bytes / 512LLU,
+			spec->rw, (long long unsigned)spec->time);
+
+	if (iip->genesis == 0) {
+		iip->genesis = spec->time;
+		if (verbose > 1)
+			fprintf(stderr, "\tSetting new genesis: %llx(%d)\n",
+				(long long unsigned)iip->genesis, iip->cpu);
+	}
+	else if (iip->genesis > spec->time)
+		fatal(NULL, ERR_SYSCALL, 
+			"Time inversion? %llu ... %llu\n",
+			(long long unsigned )iip->genesis, 
+			(long long unsigned )spec->time);
+
+	return 1;
+}
+
+/**
+ * bunch_output_hdr - Output bunch header
+ */
+static inline void bunch_output_hdr(struct io_stream *stream)
+{
+	struct io_bunch_hdr *hdrp = &stream->cur->hdr;
+
+	assert(0 < hdrp->npkts && hdrp->npkts <= BT_MAX_PKTS);
+	if (fwrite(hdrp, sizeof(struct io_bunch_hdr), 1, stream->ofp) != 1) {
+		fatal(stream->file_name, ERR_SYSCALL, "fwrite(hdr) failed\n");
+		/*NOTREACHED*/
+	}
+
+	if (verbose) {
+		__u64 off = hdrp->time_stamp - stream->iip->genesis;
+
+		assert(stream->vfp);
+		fprintf(stream->vfp, "------------------\n");
+		fprintf(stream->vfp, "%4llu.%09llu %3llu\n", 
+			(unsigned long long)off / (1000 * 1000 * 1000),
+			(unsigned long long)off % (1000 * 1000 * 1000), 
+			(unsigned long long)hdrp->npkts);
+		fprintf(stream->vfp, "------------------\n");
+	}
+}
+
+/**
+ * bunch_output_pkt - Output IO packets
+ */
+static inline void bunch_output_pkts(struct io_stream *stream)
+{
+	struct io_pkt *p = stream->cur->pkts;
+	size_t npkts = stream->cur->hdr.npkts;
+
+	assert(0 < npkts && npkts <= BT_MAX_PKTS);
+	if (fwrite(p, sizeof(struct io_pkt), npkts, stream->ofp) != npkts) {
+		fatal(stream->file_name, ERR_SYSCALL, "fwrite(pkts) failed\n");
+		/*NOTREACHED*/
+	}
+
+	if (verbose) {
+		size_t i;
+
+		assert(stream->vfp);
+		for (i = 0; i < npkts; i++, p++)
+			fprintf(stream->vfp, "\t%1d %10llu\t%10llu\n",
+				p->rw, 
+				(unsigned long long)p->sector,
+				(unsigned long long)p->nbytes / 512);
+	}
+}
+
+/**
+ * stream_flush - Flush current bunch of IOs out to the output stream
+ * @stream: Per-output file stream information
+ */
+static void stream_flush(struct io_stream *stream)
+{
+	struct io_bunch *cur = stream->cur;
+
+	if (cur) {
+		if (cur->hdr.npkts) {
+			assert(cur->hdr.npkts <= BT_MAX_PKTS);
+			bunch_output_hdr(stream);
+			bunch_output_pkts(stream);
+
+			stream->bunches++;
+			stream->pkts += cur->hdr.npkts;
+		}
+		free(cur);
+	}
+}
+
+/**
+ * bunch_done - Returns true if current bunch is either full, or next IO is late
+ * @stream: Output stream information
+ * @spec: IO trace specification
+ */
+static inline int bunch_done(struct io_stream *stream, struct io_spec *spec)
+{
+	if (stream->cur->hdr.npkts >= max_pkts_per_bunch)
+		return 1;
+
+	if ((spec->time - stream->start_time) > max_bunch_tm)
+		return 1;
+
+	return 0;
+}
+
+/**
+ * stream_add_io - Add an IO trace to the current stream
+ * @stream: Output stream information
+ * @spec: IO trace specification
+ */
+static void stream_add_io(struct io_stream *stream, struct io_spec *spec)
+{
+
+	if (stream->cur == NULL)
+		io_bunch_create(stream, spec->time);
+	else if (bunch_done(stream, spec)) {
+		stream_flush(stream);
+		io_bunch_create(stream, spec->time);
+	}
+
+	io_bunch_add(stream, spec);
+}
+
+/**
+ * stream_open - Open output stream for specified input stream
+ * @iip: Per-input file information
+ */
+static struct io_stream *stream_open(struct ifile_info *iip)
+{
+	char ofile_name[MAXPATHLEN];
+	struct io_stream *stream = malloc(sizeof(*stream));
+	struct io_file_hdr io_file_hdr = {
+		.genesis = 0,
+		.nbunches = 0,
+		.total_pkts = 0
+	};
+
+	memset(stream, 0, sizeof(*stream));
+
+	sprintf(ofile_name, "%s/%s.%s.%d", odir, iip->devnm, obase, iip->cpu);
+	stream->ofp = fopen(ofile_name, "w");
+	if (!stream->ofp) {
+		fatal(ofile_name, ERR_SYSCALL, "Open failed\n");
+		/*NOTREACHED*/
+	}
+
+	stream->iip = iip;
+	stream->cur = NULL;
+	stream->bunches = stream->pkts = 0;
+	stream->last_time = 0;
+	stream->file_name = strdup(ofile_name);
+
+	write_file_hdr(stream, &io_file_hdr);
+
+	if (verbose) {
+		char vfile_name[MAXPATHLEN];
+
+		sprintf(vfile_name, "%s/%s.%s.%d.rec", odir, iip->devnm,
+			obase, iip->cpu);
+		stream->vfp = fopen(vfile_name, "w");
+		if (!stream->vfp) {
+			fatal(vfile_name, ERR_SYSCALL, "Open failed\n");
+			/*NOTREACHED*/
+		}
+
+		stream->vfn = strdup(vfile_name);
+	}
+
+	data_is_native = -1;
+	return stream;
+}
+
+/**
+ * stream_close - Release resources associated with an output stream
+ * @stream: Stream to release
+ */
+static void stream_close(struct io_stream *stream)
+{
+	struct io_file_hdr io_file_hdr = {
+		.genesis = stream->iip->genesis,
+		.nbunches = stream->bunches,
+		.total_pkts = stream->pkts
+	};
+
+	stream_flush(stream);
+	write_file_hdr(stream, &io_file_hdr);
+	fclose(stream->ofp);
+
+	if (verbose && stream->bunches) {
+		fprintf(stderr, 
+			"%s:%d: %llu pkts (tot), %llu pkts (replay), "
+					"%llu bunches, %.1lf pkts/bunch\n",
+			stream->iip->devnm, stream->iip->cpu,
+			(unsigned long long)stream->iip->tpkts, 
+			(unsigned long long)stream->pkts, 
+			(unsigned long long)stream->bunches,
+			(double)(stream->pkts) / (double)(stream->bunches));
+
+		fclose(stream->vfp);
+		free(stream->vfn);
+	}
+
+	free(stream->file_name);
+	free(stream);
+}
+
+/**
+ * process - Process one input file to an output file
+ * @iip: Per-input file information
+ */
+static void process(struct ifile_info *iip)
+{
+	struct io_spec spec;
+	struct io_stream *stream;
+
+	stream = stream_open(iip);
+	while (next_io(iip, &spec))
+		stream_add_io(stream, &spec);
+	stream_close(stream);
+
+	rem_input_file(iip);
+}
+
+/**
+ * main - 
+ * @argc: Number of arguments
+ * @argv: Array of arguments
+ */
+int main(int argc, char *argv[])
+{
+	struct list_head *p, *q;
+
+	handle_args(argc, argv);
+	list_for_each_safe(p, q, &input_files)
+		process(list_entry(p, struct ifile_info, head));
+
+	return 0;
+}
diff --git a/btreplay/btrecord.h b/btreplay/btrecord.h
new file mode 100644
index 0000000..390e14c
--- /dev/null
+++ b/btreplay/btrecord.h
@@ -0,0 +1,95 @@
+/*
+ * Blktrace record utility - Convert binary trace data into bunches of IOs
+ *
+ * Copyright (C) 2007 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ */
+
+#if !defined(__BTRECORD_H__)
+#define __BTRECORD_H__
+
+#include <asm/types.h>
+
+#define BT_MAX_PKTS	512
+
+/*
+ * Header for each bunch
+ *
+ * @nkts: 	Number of IO packets to process
+ * @time_stamp:	Time stamp for this bunch of IOs
+ */
+struct io_bunch_hdr {
+	__u64 npkts;
+	__u64 time_stamp;
+};
+
+/*
+ * IO specifer
+ *
+ * @sector:	Sector number of IO
+ * @nbytes:	Number of bytes to process
+ * @rw:		IO direction: 0 = write, 1 = read
+ */
+struct io_pkt {
+	__u64 sector;
+	__u64 nbytes;
+	__u32 rw;
+};
+
+/*
+ * Shorthand notion of a bunch of IOs
+ *
+ * @hdr: 	Header describing stall and how many IO packets follow
+ * @pkts: 	Individual IOs are described here
+ */
+struct io_bunch {
+	struct io_bunch_hdr hdr;
+	struct io_pkt pkts[BT_MAX_PKTS];
+};
+
+/*
+ * Header for each recorded file
+ *
+ * @version:	Version information
+ * @genesis:	Time stamp for earliest bunch
+ * @nbunches:	Number of bunches put into the file
+ * @total_pkts:	Number of packets to be processed
+ */
+struct io_file_hdr {
+	__u64 version;
+	__u64 genesis;
+	__u64 nbunches;
+	__u64 total_pkts;
+};
+
+static inline __u64 mk_btversion(int mjr, int mnr, int sub)
+{
+	return ((mjr & 0xff) << 16) | ((mnr & 0xff) << 8) | (sub & 0xff);
+}
+
+static inline void get_btversion(__u64 version, int *mjr, int *mnr, int *sub)
+{
+	*mjr = (int)((version >> 16) & 0xff);
+	*mnr = (int)((version >>  8) & 0xff);
+	*sub = (int)((version >>  0) & 0xff);
+}
+
+static char my_btversion[] = "1.0.0";
+static int btver_mjr = 1;
+static int btver_mnr = 0;
+static int btver_sub = 0;
+
+#endif
diff --git a/btreplay/btreplay.c b/btreplay/btreplay.c
new file mode 100644
index 0000000..5444010
--- /dev/null
+++ b/btreplay/btreplay.c
@@ -0,0 +1,1655 @@
+/*
+ * Blktrace replay utility - Play traces back
+ *
+ * Copyright (C) 2007 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ */
+
+static char build_date[] = __DATE__ " at "__TIME__;
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libaio.h>
+#include <pthread.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <stdarg.h>
+
+#if !defined(_GNU_SOURCE)
+#	define _GNU_SOURCE
+#endif
+#include <getopt.h>
+
+#include "list.h"
+#include "btrecord.h"
+
+/* 
+ * ========================================================================
+ * ==== STRUCTURE DEFINITIONS =============================================
+ * ========================================================================
+ */
+
+/**
+ * Each device map has one of these:
+ * 
+ * @head:	Linked on to map_devs
+ * @from_dev:	Device name as seen on recorded system
+ * @to_dev:	Device name to be used on replay system
+ */
+struct map_dev {
+	struct list_head head;
+	char *from_dev, *to_dev;
+};
+
+/**
+ * Each device name specified has one of these (until threads are created)
+ *
+ * @head: 	Linked onto input_devs
+ * @devnm: 	Device name -- 'sd*'
+ */
+struct dev_info {
+	struct list_head head;
+	char *devnm;
+};
+
+/*
+ * Per input file information
+ *
+ * @head: 	Used to link up on input_files
+ * @free_iocbs: List of free iocb's available for use
+ * @used_iocbs: List of iocb's currently outstanding
+ * @mutex: 	Mutex used with condition variable to protect volatile values
+ * @cond: 	Condition variable used when waiting on a volatile value change
+ * @naios_out: 	Current number of AIOs outstanding on this context
+ * @naios_free: Number of AIOs on the free list (short cut for list_len)
+ * @send_wait: 	Boolean: When true, the sub thread is waiting on free IOCBs
+ * @reap_wait: 	Boolean: When true, the rec thread is waiting on used IOCBs
+ * @send_done: 	Boolean: When true, the sub thread has completed work
+ * @reap_done: 	Boolean: When true, the rec thread has completed work
+ * @sub_thread: Thread used to submit IOs.
+ * @rec_thread: Thread used to reclaim IOs.
+ * @ctx: 	IO context
+ * @devnm: 	Copy of the device name being managed by this thread
+ * @file_name: 	Full name of the input file
+ * @cpu: 	CPU this thread is pinned to
+ * @ifd: 	Input file descriptor
+ * @ofd: 	Output file descriptor
+ * @iterations: Remaining iterations to process
+ * @vfp:	For verbose dumping of actions performed
+ */
+struct thr_info {
+	struct list_head head, free_iocbs, used_iocbs;
+	pthread_mutex_t mutex;
+	pthread_cond_t cond;
+	volatile long naios_out, naios_free;
+	volatile int send_wait, reap_wait, send_done, reap_done;
+	pthread_t sub_thread, rec_thread;
+	io_context_t ctx;
+	char *devnm, *file_name;
+	int cpu, ifd, ofd, iterations;
+	FILE *vfp;
+};
+
+/*
+ * Every Asynchronous IO used has one of these (naios per file/device).
+ *
+ * @iocb:	IOCB sent down via io_submit
+ * @head:	Linked onto file_list.free_iocbs or file_list.used_iocbs
+ * @tip:	Pointer to per-thread information this IO is associated with
+ * @nbytes:	Number of bytes in buffer associated with iocb
+ */
+struct iocb_pkt {
+	struct iocb iocb;
+	struct list_head head;
+	struct thr_info *tip;
+	int nbytes;
+};
+
+/* 
+ * ========================================================================
+ * ==== GLOBAL VARIABLES ==================================================
+ * ========================================================================
+ */
+
+static volatile int signal_done = 0;	// Boolean: Signal'ed, need to quit
+
+static char *ibase = "replay";		// Input base name
+static char *idir = ".";		// Input directory base
+static int cpus_to_use = -1;		// Number of CPUs to use
+static int def_iterations = 1;		// Default number of iterations
+static int naios = 512;			// Number of AIOs per thread
+static int ncpus = 0;			// Number of CPUs in the system
+static int verbose = 0;			// Boolean: Output some extra info
+static int write_enabled = 0;		// Boolean: Enable writing
+static __u64 genesis = ~0;		// Earliest time seen
+static __u64 rgenesis;			// Our start time
+static size_t pgsize;			// System Page size
+static int nb_sec = 512;		// Number of bytes per sector
+static LIST_HEAD(input_devs);		// List of devices to handle
+static LIST_HEAD(input_files);		// List of input files to handle
+static LIST_HEAD(map_devs);		// List of device maps
+static int nfiles = 0;			// Number of files to handle
+static int no_stalls = 0;		// Boolean: Disable pre-stalls
+static unsigned acc_factor = 1;		// Int: Acceleration factor
+static int find_records = 0;		// Boolean: Find record files auto
+
+/*
+ * Variables managed under control of condition variables.
+ *
+ * n_reclaims_done: 	Counts number of reclaim threads that have completed.
+ * n_replays_done:	Counts number of replay threads that have completed.
+ * n_replays_ready:	Counts number of replay threads ready to start.
+ * n_iters_done:	Counts number of replay threads done one iteration.
+ * iter_start:		Starts an iteration for the replay threads.
+ */
+static volatile int n_reclaims_done = 0;
+static pthread_mutex_t reclaim_done_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t reclaim_done_cond = PTHREAD_COND_INITIALIZER;
+
+static volatile int n_replays_done = 0;
+static pthread_mutex_t replay_done_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t replay_done_cond = PTHREAD_COND_INITIALIZER;
+
+static volatile int n_replays_ready = 0;
+static pthread_mutex_t replay_ready_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t replay_ready_cond = PTHREAD_COND_INITIALIZER;
+
+static volatile int n_iters_done = 0;
+static pthread_mutex_t iter_done_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t iter_done_cond = PTHREAD_COND_INITIALIZER;
+
+static volatile int iter_start = 0;
+static pthread_mutex_t iter_start_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t iter_start_cond = PTHREAD_COND_INITIALIZER;
+
+/* 
+ * ========================================================================
+ * ==== FORWARD REFERENECES ===============================================
+ * ========================================================================
+ */
+
+static void *replay_sub(void *arg);
+static void *replay_rec(void *arg);
+static char usage_str[];
+
+/* 
+ * ========================================================================
+ * ==== INLINE ROUTINES ===================================================
+ * ========================================================================
+ */
+
+/*
+ * The 'fatal' macro will output a perror message (if errstring is !NULL)
+ * and display a string (with variable arguments) and then exit with the 
+ * specified exit value.
+ */
+#define ERR_ARGS			1
+#define ERR_SYSCALL			2
+static inline void fatal(const char *errstring, const int exitval,
+			 const char *fmt, ...)
+{
+	va_list ap;
+
+	if (errstring)
+		perror(errstring);
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+
+	exit(exitval);
+	/*NOTREACHED*/
+}
+
+static inline long long unsigned du64_to_sec(__u64 du64)
+{
+	return (long long unsigned)du64 / (1000 * 1000 * 1000);
+}
+
+static inline long long unsigned du64_to_nsec(__u64 du64)
+{
+	return llabs((long long)du64) % (1000 * 1000 * 1000);
+}
+
+/**
+ * min - Return minimum of two integers
+ */
+static inline int min(int a, int b)
+{ 
+	return a < b ? a : b;
+}
+
+/**
+ * minl - Return minimum of two longs
+ */
+static inline long minl(long a, long b)
+{ 
+	return a < b ? a : b;
+}
+
+/**
+ * usage - Display usage string and version
+ */
+static inline void usage(void)
+{
+	fprintf(stderr, "Usage: btreplay -- version %s\n%s", 
+		my_btversion, usage_str);
+}
+
+/**
+ * is_send_done - Returns true if sender should quit early
+ * @tip: Per-thread information
+ */
+static inline int is_send_done(struct thr_info *tip)
+{
+	return signal_done || tip->send_done;
+}
+
+/**
+ * is_reap_done - Returns true if reaper should quit early
+ * @tip: Per-thread information
+ */
+static inline int is_reap_done(struct thr_info *tip)
+{
+	return tip->send_done && tip->naios_out == 0;
+}
+
+/**
+ * ts2ns - Convert timespec values to a nanosecond value
+ */
+#define NS_TICKS		((__u64)1000 * (__u64)1000 * (__u64)1000)
+static inline __u64 ts2ns(struct timespec *ts)
+{
+	return ((__u64)(ts->tv_sec) * NS_TICKS) + (__u64)(ts->tv_nsec);
+}
+
+/**
+ * ts2ns - Convert timeval values to a nanosecond value
+ */
+static inline __u64 tv2ns(struct timeval *tp)
+{
+	return ((__u64)(tp->tv_sec)) + ((__u64)(tp->tv_usec) * (__u64)1000);
+}
+
+/**
+ * touch_memory - Force physical memory to be allocating it
+ * 
+ * For malloc()ed memory we need to /touch/ it to make it really
+ * exist. Otherwise, for write's (to storage) things may not work
+ * as planned - we see Linux just use a single area to /read/ from
+ * (as there isn't any memory that has been associated with the 
+ * allocated virtual addresses yet).
+ */
+static inline void touch_memory(char *buf, size_t bsize)
+{
+#if defined(PREP_BUFS)
+	memset(buf, 0, bsize);
+#else
+	size_t i;
+
+	for (i = 0; i < bsize; i += pgsize)
+		buf[i] = 0;
+#endif
+}
+
+/**
+ * buf_alloc - Returns a page-aligned buffer of the specified size
+ * @nbytes: Number of bytes to allocate
+ */
+static inline void *buf_alloc(size_t nbytes)
+{
+	void *buf;
+
+	if (posix_memalign(&buf, pgsize, nbytes)) {
+		fatal("posix_memalign", ERR_SYSCALL, "Allocation failed\n");
+		/*NOTREACHED*/
+	}
+
+	return buf;
+}
+
+/**
+ * gettime - Returns current time 
+ */
+static inline __u64 gettime(void)
+{
+	static int use_clock_gettime = -1;		// Which clock to use
+
+	if (use_clock_gettime < 0) {
+		use_clock_gettime = clock_getres(CLOCK_MONOTONIC, NULL) == 0;
+		if (use_clock_gettime) {
+			struct timespec ts = {
+				.tv_sec = 0,
+				.tv_nsec = 0
+			};
+			clock_settime(CLOCK_MONOTONIC, &ts);
+		}
+	}
+
+	if (use_clock_gettime) {
+		struct timespec ts;
+		clock_gettime(CLOCK_MONOTONIC, &ts);
+		return ts2ns(&ts);
+	}
+	else {
+		struct timeval tp;
+		gettimeofday(&tp, NULL);
+		return tv2ns(&tp);
+	}
+}
+
+/**
+ * setup_signal - Set up a signal handler for the specified signum
+ */
+static inline void setup_signal(int signum, sighandler_t handler)
+{
+	if (signal(signum, handler) == SIG_ERR) {
+		fatal("signal", ERR_SYSCALL, "Failed to set signal %d\n",
+			signum);
+		/*NOTREACHED*/
+	}
+}
+
+/* 
+ * ========================================================================
+ * ==== CONDITION VARIABLE ROUTINES =======================================
+ * ========================================================================
+ */
+
+/**
+ * __set_cv - Increments a variable under condition variable control.
+ * @pmp: 	Pointer to the associated mutex
+ * @pcp: 	Pointer to the associated condition variable
+ * @vp: 	Pointer to the variable being incremented
+ * @mxv: 	Max value for variable (Used only when ASSERTS are on)
+ */
+static inline void __set_cv(pthread_mutex_t *pmp, pthread_cond_t *pcp,
+			    volatile int *vp, 
+			    __attribute__((__unused__))int mxv)
+{
+	pthread_mutex_lock(pmp);
+	assert(*vp < mxv);
+	*vp += 1;
+	pthread_cond_signal(pcp);
+	pthread_mutex_unlock(pmp);
+}
+
+/**
+ * __wait_cv - Waits for a variable under cond var control to hit a value
+ * @pmp: 	Pointer to the associated mutex
+ * @pcp: 	Pointer to the associated condition variable
+ * @vp: 	Pointer to the variable being incremented
+ * @mxv: 	Value to wait for
+ */
+static inline void __wait_cv(pthread_mutex_t *pmp, pthread_cond_t *pcp,
+			     volatile int *vp, int mxv)
+{
+	pthread_mutex_lock(pmp);
+	while (*vp < mxv)
+		pthread_cond_wait(pcp, pmp);
+	*vp = 0;
+	pthread_mutex_unlock(pmp);
+}
+
+static inline void set_reclaim_done(void)
+{
+	__set_cv(&reclaim_done_mutex, &reclaim_done_cond, &n_reclaims_done,
+		 nfiles);
+}
+
+static inline void wait_reclaims_done(void)
+{
+	__wait_cv(&reclaim_done_mutex, &reclaim_done_cond, &n_reclaims_done,
+		  nfiles);
+}
+
+static inline void set_replay_ready(void)
+{
+	__set_cv(&replay_ready_mutex, &replay_ready_cond, &n_replays_ready,
+		 nfiles);
+}
+
+static inline void wait_replays_ready(void)
+{
+	__wait_cv(&replay_ready_mutex, &replay_ready_cond, &n_replays_ready,
+		  nfiles);
+}
+
+static inline void set_replay_done(void)
+{
+	__set_cv(&replay_done_mutex, &replay_done_cond, &n_replays_done,
+		nfiles);
+}
+
+static inline void wait_replays_done(void)
+{
+	__wait_cv(&replay_done_mutex, &replay_done_cond, &n_replays_done,
+		  nfiles);
+}
+
+static inline void set_iter_done(void)
+{
+	__set_cv(&iter_done_mutex, &iter_done_cond, &n_iters_done,
+		nfiles);
+}
+
+static inline void wait_iters_done(void)
+{
+	__wait_cv(&iter_done_mutex, &iter_done_cond, &n_iters_done,
+		  nfiles);
+}
+
+/**
+ * wait_iter_start - Wait for an iteration to start 
+ * 
+ * This is /slightly/ different: we are waiting for a value to become
+ * non-zero, and then we decrement it and go on. 
+ */
+static inline void wait_iter_start(void)
+{
+	pthread_mutex_lock(&iter_start_mutex);
+	while (iter_start == 0)
+		pthread_cond_wait(&iter_start_cond, &iter_start_mutex);
+	assert(1 <= iter_start && iter_start <= nfiles);
+	iter_start--;
+	pthread_mutex_unlock(&iter_start_mutex);
+}
+
+/**
+ * start_iter - Start an iteration at the replay thread level
+ */
+static inline void start_iter(void)
+{
+	pthread_mutex_lock(&iter_start_mutex);
+	assert(iter_start == 0);
+	iter_start = nfiles;
+	pthread_cond_broadcast(&iter_start_cond);
+	pthread_mutex_unlock(&iter_start_mutex);
+}
+
+/* 
+ * ========================================================================
+ * ==== CPU RELATED ROUTINES ==============================================
+ * ========================================================================
+ */
+
+/**
+ * get_ncpus - Sets up the global 'ncpus' value
+ */
+static void get_ncpus(void)
+{
+#ifdef _SC_NPROCESSORS_CONF
+	ncpus = sysconf(_SC_NPROCESSORS_CONF);
+#else
+	int nrcpus = 4096;
+	cpu_set_t * cpus;
+	
+realloc:
+	cpus = CPU_ALLOC(nrcpus);
+	size = CPU_ALLOC_SIZE(nrcpus);
+	CPU_ZERO_S(size, cpus);
+
+	if (sched_getaffinity(getpid(), size, cpus)) {
+		if( errno == EINVAL && nrcpus < (4096<<4) ) {
+			CPU_FREE(cpus);
+			nrcpus <= 1;
+			goto realloc;
+		}
+		fatal("sched_getaffinity", ERR_SYSCALL, "Can't get CPU info\n");
+		/*NOTREACHED*/
+	}
+
+	ncpus = -1;
+	for (last_cpu = 0; last_cpu < CPU_SETSIZE && CPU_ISSET(last_cpu, &cpus); last_cpu++)
+		if (CPU_ISSET( last_cpu, &cpus) ) 
+			ncpus = last_cpu;
+	ncpus++;
+	CPU_FREE(cpus);
+#endif
+	if (ncpus == 0) {
+		fatal(NULL, ERR_SYSCALL, "Insufficient number of CPUs\n");
+		/*NOTREACHED*/
+	}
+}
+
+/**
+ * pin_to_cpu - Pin this thread to a specific CPU
+ * @tip: Thread information
+ */
+static void pin_to_cpu(struct thr_info *tip)
+{
+	cpu_set_t *cpus;
+	size_t size;
+
+	cpus = CPU_ALLOC(ncpus);
+	size = CPU_ALLOC_SIZE(ncpus);	
+
+	assert(0 <= tip->cpu && tip->cpu < ncpus);
+
+	CPU_ZERO_S(ncpus, cpus);
+	CPU_SET_S(tip->cpu, size, cpus);
+	if (sched_setaffinity(getpid(), size, cpus)) {
+		fatal("sched_setaffinity", ERR_SYSCALL, "Failed to pin CPU\n");
+		/*NOTREACHED*/
+	}
+
+	if (verbose > 1) {
+		int i;
+		cpu_set_t *now = CPU_ALLOC(ncpus);
+
+		(void)sched_getaffinity(getpid(), size, now);
+		fprintf(tip->vfp, "Pinned to CPU %02d ", tip->cpu);
+		for (i = 0; i < ncpus; i++)
+			fprintf(tip->vfp, "%1d", CPU_ISSET_S(i, size, now));
+		fprintf(tip->vfp, "\n");
+	}
+}
+
+/* 
+ * ========================================================================
+ * ==== INPUT DEVICE HANDLERS =============================================
+ * ========================================================================
+ */
+
+/**
+ * add_input_dev - Add a device ('sd*') to the list of devices to handle
+ */
+static void add_input_dev(char *devnm)
+{
+	struct list_head *p;
+	struct dev_info *dip;
+
+	__list_for_each(p, &input_devs) {
+		dip = list_entry(p, struct dev_info, head);
+		if (strcmp(dip->devnm, devnm) == 0)
+			return;
+	}
+
+	dip = malloc(sizeof(*dip));
+	dip->devnm = strdup(devnm);
+	list_add_tail(&dip->head, &input_devs);
+}
+
+/**
+ * rem_input_dev - Remove resources associated with this device
+ */
+static void rem_input_dev(struct dev_info *dip)
+{
+	list_del(&dip->head);
+	free(dip->devnm);
+	free(dip);
+}
+
+static void find_input_devs(char *idir)
+{
+	struct dirent *ent;
+	DIR *dir = opendir(idir);
+
+	if (dir == NULL) {
+		fatal(idir, ERR_ARGS, "Unable to open %s\n", idir);
+		/*NOTREACHED*/
+	}
+
+	while ((ent = readdir(dir)) != NULL) {
+		char *p, *dsf;
+
+		if (strstr(ent->d_name, ".replay.") == NULL)
+			continue;
+
+		dsf = strdup(ent->d_name);
+		p = index(dsf, '.');
+		assert(p != NULL);
+		*p = '\0';
+		add_input_dev(dsf);
+		free(dsf);
+	}
+
+	closedir(dir);
+}
+
+/* 
+ * ========================================================================
+ * ==== MAP DEVICE INTERFACES =============================================
+ * ========================================================================
+ */
+
+/**
+ * read_map_devs - Read in a set of device mapping from the provided file.
+ * @file_name:	File containing device maps
+ *
+ * We support the notion of multiple such files being specifed on the cmd line
+ */
+static void read_map_devs(char *file_name)
+{
+	FILE *fp;
+	char *from_dev, *to_dev;
+
+	fp = fopen(file_name, "r");
+	if (!fp) {
+		fatal(file_name, ERR_SYSCALL, "Could not open map devs file\n");
+		/*NOTREACHED*/
+	}
+
+	while (fscanf(fp, "%as %as", &from_dev, &to_dev) == 2) {
+		struct map_dev *mdp = malloc(sizeof(*mdp));
+
+		mdp->from_dev = from_dev;
+		mdp->to_dev = to_dev;
+		list_add_tail(&mdp->head, &map_devs);
+	}
+
+	fclose(fp);
+}
+
+/**
+ * release_map_devs - Release resources associated with device mappings.
+ */
+static void release_map_devs(void)
+{
+	struct list_head *p, *q;
+
+	list_for_each_safe(p, q, &map_devs) {
+		struct map_dev *mdp = list_entry(p, struct map_dev, head);
+
+		list_del(&mdp->head);
+
+		free(mdp->from_dev);
+		free(mdp->to_dev);
+		free(mdp);
+	}
+}
+
+/**
+ * map_dev - Return the mapped device for that specified
+ * @from_dev:	Device name as seen on recorded system
+ *
+ * Note: If there is no such mapping, we return the same name.
+ */
+static char *map_dev(char *from_dev)
+{
+	struct list_head *p;
+
+	__list_for_each(p, &map_devs) {
+		struct map_dev *mdp = list_entry(p, struct map_dev, head);
+
+		if (strcmp(from_dev, mdp->from_dev) == 0)
+			return mdp->to_dev;
+	}
+
+	return from_dev;
+}
+
+/* 
+ * ========================================================================
+ * ==== IOCB MANAGEMENT ROUTINES ==========================================
+ * ========================================================================
+ */
+
+/**
+ * iocb_init - Initialize the fields of an IOCB
+ * @tip: Per-thread information
+ * iocbp: IOCB pointer to update
+ */
+static void iocb_init(struct thr_info *tip, struct iocb_pkt *iocbp)
+{
+	iocbp->tip = tip;
+	iocbp->nbytes = 0;
+	iocbp->iocb.u.c.buf = NULL;
+}
+
+/**
+ * iocb_setup - Set up an iocb with this AIOs information
+ * @iocbp: IOCB pointer to update
+ * @rw: Direction (0 == write, 1 == read)
+ * @n: Number of bytes to transfer
+ * @off: Offset (in bytes)
+ */
+static void iocb_setup(struct iocb_pkt *iocbp, int rw, int n, long long off)
+{
+	char *buf;
+	struct iocb *iop = &iocbp->iocb;
+
+	assert(rw == 0 || rw == 1);
+	assert(0 < n && (n % nb_sec) == 0);
+	assert(0 <= off);
+
+	if (iocbp->nbytes) {
+		if (iocbp->nbytes >= n) {
+			buf = iop->u.c.buf;
+			goto prep;
+		}
+
+		assert(iop->u.c.buf);
+		free(iop->u.c.buf);
+	}
+
+	buf = buf_alloc(n);
+	iocbp->nbytes = n;
+
+prep:
+	if (rw)
+		io_prep_pread(iop, iocbp->tip->ofd, buf, n, off);
+	else {
+		assert(write_enabled);
+		io_prep_pwrite(iop, iocbp->tip->ofd, buf, n, off);
+		touch_memory(buf, n);
+	}
+
+	iop->data = iocbp;
+}
+
+/* 
+ * ========================================================================
+ * ==== PER-THREAD SET UP & TEAR DOWN =====================================
+ * ========================================================================
+ */
+
+/**
+ * tip_init - Per thread initialization function
+ */
+static void tip_init(struct thr_info *tip)
+{
+	int i;
+
+	INIT_LIST_HEAD(&tip->free_iocbs);
+	INIT_LIST_HEAD(&tip->used_iocbs);
+
+	pthread_mutex_init(&tip->mutex, NULL);
+	pthread_cond_init(&tip->cond, NULL);
+
+	if (io_setup(naios, &tip->ctx)) {
+		fatal("io_setup", ERR_SYSCALL, "io_setup failed\n");
+		/*NOTREACHED*/
+	}
+
+	tip->ofd = -1;
+	tip->naios_out = 0;
+	tip->send_done = tip->reap_done = 0;
+	tip->send_wait = tip->reap_wait = 0;
+
+	memset(&tip->sub_thread, 0, sizeof(tip->sub_thread));
+	memset(&tip->rec_thread, 0, sizeof(tip->rec_thread));
+
+	for (i = 0; i < naios; i++) {
+		struct iocb_pkt *iocbp = buf_alloc(sizeof(*iocbp));
+
+		iocb_init(tip, iocbp);
+		list_add_tail(&iocbp->head, &tip->free_iocbs);
+	}
+	tip->naios_free = naios;
+
+	if (verbose > 1) {
+		char fn[MAXPATHLEN];
+
+		sprintf(fn, "%s/%s.%s.%d.rep", idir, tip->devnm, ibase, 
+			tip->cpu);
+		tip->vfp = fopen(fn, "w");
+		if (!tip->vfp) {
+			fatal(fn, ERR_SYSCALL, "Failed to open report\n");
+			/*NOTREACHED*/
+		}
+
+		setlinebuf(tip->vfp);
+	}
+
+	if (pthread_create(&tip->sub_thread, NULL, replay_sub, tip)) {
+		fatal("pthread_create", ERR_SYSCALL, 
+			"thread create failed\n");
+		/*NOTREACHED*/
+	}
+
+	if (pthread_create(&tip->rec_thread, NULL, replay_rec, tip)) {
+		fatal("pthread_create", ERR_SYSCALL, 
+			"thread create failed\n");
+		/*NOTREACHED*/
+	}
+}
+
+/**
+ * tip_release - Release resources associated with this thread
+ */
+static void tip_release(struct thr_info *tip)
+{
+	struct list_head *p, *q;
+
+	assert(tip->send_done);
+	assert(tip->reap_done);
+	assert(list_len(&tip->used_iocbs) == 0);
+	assert(tip->naios_free == naios);
+
+	if (pthread_join(tip->sub_thread, NULL)) {
+		fatal("pthread_join", ERR_SYSCALL, "pthread sub join failed\n");
+		/*NOTREACHED*/
+	}
+	if (pthread_join(tip->rec_thread, NULL)) {
+		fatal("pthread_join", ERR_SYSCALL, "pthread rec join failed\n");
+		/*NOTREACHED*/
+	}
+
+	io_destroy(tip->ctx);
+
+	list_splice(&tip->used_iocbs, &tip->free_iocbs);
+	list_for_each_safe(p, q, &tip->free_iocbs) {
+		struct iocb_pkt *iocbp = list_entry(p, struct iocb_pkt, head);
+
+		list_del(&iocbp->head);
+		if (iocbp->nbytes) 
+			free(iocbp->iocb.u.c.buf);
+		free(iocbp);
+	}
+
+	pthread_cond_destroy(&tip->cond);
+	pthread_mutex_destroy(&tip->mutex);
+}
+
+/**
+ * add_input_file - Allocate and initialize per-input file structure
+ * @cpu: CPU for this file
+ * @devnm: Device name for this file
+ * @file_name: Fully qualifed input file name
+ */
+static void add_input_file(int cpu, char *devnm, char *file_name)
+{
+	struct stat buf;
+	struct io_file_hdr hdr;
+	struct thr_info *tip = buf_alloc(sizeof(*tip));
+	__u64 my_version = mk_btversion(btver_mjr, btver_mnr, btver_sub);
+
+	assert(0 <= cpu && cpu < ncpus);
+
+	memset(&hdr, 0, sizeof(hdr));
+	memset(tip, 0, sizeof(*tip));
+	tip->cpu = cpu % cpus_to_use;
+	tip->iterations = def_iterations;
+
+	tip->ifd = open(file_name, O_RDONLY);
+	if (tip->ifd < 0) {
+		fatal(file_name, ERR_ARGS, "Unable to open\n");
+		/*NOTREACHED*/
+	}
+	if (fstat(tip->ifd, &buf) < 0) {
+		fatal(file_name, ERR_SYSCALL, "fstat failed\n");
+		/*NOTREACHED*/
+	}
+	if (buf.st_size < (off_t)sizeof(hdr)) {
+		if (verbose)
+			fprintf(stderr, "\t%s empty\n", file_name);
+		goto empty_file;
+	}
+
+	if (read(tip->ifd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+		fatal(file_name, ERR_ARGS, "Header read failed\n");
+		/*NOTREACHED*/
+	}
+
+	if (hdr.version != my_version) {
+		fprintf(stderr, "%llx %llx %llx %llx\n", 
+			(long long unsigned)hdr.version,
+			(long long unsigned)hdr.genesis,
+			(long long unsigned)hdr.nbunches,
+			(long long unsigned)hdr.total_pkts);
+		fatal(NULL, ERR_ARGS, 
+			"BT version mismatch: %lx versus my %lx\n",
+			(long)hdr.version, (long)my_version);
+			
+	}
+
+	if (hdr.nbunches == 0) {
+empty_file:
+		close(tip->ifd);
+		free(tip);
+		return;
+	}
+
+	if (hdr.genesis < genesis) {
+		if (verbose > 1)
+			fprintf(stderr, "Setting genesis to %llu.%llu\n",
+				du64_to_sec(hdr.genesis),
+				du64_to_nsec(hdr.genesis));
+		genesis = hdr.genesis;
+	}
+
+	tip->devnm = strdup(devnm);
+	tip->file_name = strdup(file_name);
+
+	list_add_tail(&tip->head, &input_files);
+
+	if (verbose)
+		fprintf(stderr, "Added %s %llu\n", file_name, 
+			(long long)hdr.genesis);
+}
+
+/**
+ * rem_input_file - Release resources associated with an input file
+ * @tip: Per-input file information
+ */
+static void rem_input_file(struct thr_info *tip)
+{
+	list_del(&tip->head);
+
+	tip_release(tip);
+
+	close(tip->ofd);
+	close(tip->ifd);
+	free(tip->file_name);
+	free(tip->devnm);
+	free(tip);
+}
+
+/**
+ * rem_input_files - Remove all input files
+ */
+static void rem_input_files(void)
+{
+	struct list_head *p, *q;
+
+	list_for_each_safe(p, q, &input_files) {
+		rem_input_file(list_entry(p, struct thr_info, head));
+	}
+}
+
+/**
+ * __find_input_files - Find input files associated with this device (per cpu)
+ */
+static void __find_input_files(struct dev_info *dip)
+{
+	int cpu = 0;
+
+	for (;;) {
+		char full_name[MAXPATHLEN];
+
+		sprintf(full_name, "%s/%s.%s.%d", idir, dip->devnm, ibase, cpu);
+		if (access(full_name, R_OK) != 0)
+			break;
+
+		add_input_file(cpu, dip->devnm, full_name);
+		cpu++;
+	}
+
+	if (!cpu) {
+		fatal(NULL, ERR_ARGS, "No traces found for %s\n", dip->devnm);
+		/*NOTREACHED*/
+	}
+
+	rem_input_dev(dip);
+}
+
+
+/**
+ * find_input_files - Find input files for all devices
+ */
+static void find_input_files(void)
+{
+	struct list_head *p, *q;
+
+	list_for_each_safe(p, q, &input_devs) {
+		__find_input_files(list_entry(p, struct dev_info, head));
+	}
+}
+
+/* 
+ * ========================================================================
+ * ==== RECLAIM ROUTINES ==================================================
+ * ========================================================================
+ */
+
+/**
+ * reap_wait_aios - Wait for and return number of outstanding AIOs
+ *
+ * Will return 0 if we are done
+ */
+static int reap_wait_aios(struct thr_info *tip)
+{
+	int naios = 0;
+
+	if (!is_reap_done(tip)) {
+		pthread_mutex_lock(&tip->mutex);
+		while (tip->naios_out == 0) {
+			tip->reap_wait = 1;
+			if (pthread_cond_wait(&tip->cond, &tip->mutex)) {
+				fatal("pthread_cond_wait", ERR_SYSCALL, 
+					"nfree_current cond wait failed\n");
+				/*NOTREACHED*/
+			}
+		}
+		naios = tip->naios_out;
+		pthread_mutex_unlock(&tip->mutex);
+	}
+	assert(is_reap_done(tip) || naios > 0);
+
+	return is_reap_done(tip) ? 0 : naios;
+}
+
+/**
+ * reclaim_ios - Reclaim AIOs completed, recycle IOCBs
+ * @tip: Per-thread information
+ * @naios_out: Number of AIOs we have outstanding (min)
+ */
+static void reclaim_ios(struct thr_info *tip, long naios_out)
+{
+	long i, ndone;
+	struct io_event *evp, events[naios_out];
+
+again:
+	assert(naios > 0);
+	for (;;) {
+		ndone = io_getevents(tip->ctx, 1, naios_out, events, NULL);
+		if (ndone > 0)
+			break;
+
+		if (errno && errno != EINTR) {
+			fatal("io_getevents", ERR_SYSCALL, 
+				"io_getevents failed\n");
+			/*NOTREACHED*/
+		}
+	}
+	assert(0 < ndone && ndone <= naios_out);
+
+	pthread_mutex_lock(&tip->mutex);
+	for (i = 0, evp = events; i < ndone; i++, evp++) {
+		struct iocb_pkt *iocbp = evp->data;
+
+                if (evp->res != iocbp->iocb.u.c.nbytes) {
+                        fatal(NULL, ERR_SYSCALL,
+                              "Event failure %ld/%ld\t(%ld + %ld)\n",
+                              (long)evp->res, (long)evp->res2,
+                              (long)iocbp->iocb.u.c.offset / nb_sec, 
+			      (long)iocbp->iocb.u.c.nbytes / nb_sec);
+                        /*NOTREACHED*/
+                }
+
+		list_move_tail(&iocbp->head, &tip->free_iocbs);
+	}
+
+	tip->naios_free += ndone;
+	tip->naios_out -= ndone;
+	naios_out = minl(naios_out, tip->naios_out);
+
+	if (tip->send_wait) {
+		tip->send_wait = 0;
+		pthread_cond_signal(&tip->cond);
+	}
+	pthread_mutex_unlock(&tip->mutex);
+
+	/*
+	 * Short cut: If we /know/ there are some more AIOs, go handle them
+	 */
+	if (naios_out)
+		goto again;
+}
+
+/**
+ * replay_rec - Worker thread to reclaim AIOs
+ * @arg: Pointer to thread information
+ */
+static void *replay_rec(void *arg)
+{
+	long naios_out;
+	struct thr_info *tip = arg;
+
+	while ((naios_out = reap_wait_aios(tip)) > 0) 
+		reclaim_ios(tip, naios_out);
+
+	assert(tip->send_done);
+	tip->reap_done = 1;
+	set_reclaim_done();
+
+	return NULL;
+}
+
+/* 
+ * ========================================================================
+ * ==== REPLAY ROUTINES ===================================================
+ * ========================================================================
+ */
+
+/**
+ * next_bunch - Retrieve next bunch of AIOs to process
+ * @tip: Per-thread information
+ * @bunch: Bunch information
+ *
+ * Returns TRUE if we recovered a bunch of IOs, else hit EOF
+ */
+static int next_bunch(struct thr_info *tip, struct io_bunch *bunch)
+{
+	size_t count, result;
+	
+	result = read(tip->ifd, &bunch->hdr, sizeof(bunch->hdr));
+	if (result != sizeof(bunch->hdr)) {
+		if (result == 0)
+			return 0;
+
+		fatal(tip->file_name, ERR_SYSCALL, "Short hdr(%ld)\n", 
+			(long)result);
+		/*NOTREACHED*/
+	}
+	assert(bunch->hdr.npkts <= BT_MAX_PKTS);
+
+	count = bunch->hdr.npkts * sizeof(struct io_pkt);
+	result = read(tip->ifd, &bunch->pkts, count);
+	if (result != count) {
+		fatal(tip->file_name, ERR_SYSCALL, "Short pkts(%ld/%ld)\n", 
+			(long)result, (long)count);
+		/*NOTREACHED*/
+	}
+
+	return 1;
+}
+
+/**
+ * nfree_current - Returns current number of AIOs that are free
+ *
+ * Will wait for available ones...
+ *
+ * Returns 0 if we have some condition that causes us to exit
+ */
+static int nfree_current(struct thr_info *tip)
+{
+	int nfree = 0;
+
+	pthread_mutex_lock(&tip->mutex);
+	while (!is_send_done(tip) && ((nfree = tip->naios_free) == 0)) {
+		tip->send_wait = 1;
+		if (pthread_cond_wait(&tip->cond, &tip->mutex)) {
+			fatal("pthread_cond_wait", ERR_SYSCALL, 
+				"nfree_current cond wait failed\n");
+			/*NOTREACHED*/
+		}
+	}
+	pthread_mutex_unlock(&tip->mutex);
+
+	return nfree;
+}
+
+/**
+ * stall - Stall for the number of nanoseconds requested
+ *
+ * We may be late, in which case we just return.
+ */
+static void stall(struct thr_info *tip, long long oclock)
+{
+	struct timespec req;
+	long long dreal, tclock = gettime() - rgenesis;
+
+	oclock /= acc_factor;
+	
+	if (verbose > 1)
+		fprintf(tip->vfp, "   stall(%lld.%09lld, %lld.%09lld)\n",
+			du64_to_sec(oclock), du64_to_nsec(oclock),
+			du64_to_sec(tclock), du64_to_nsec(tclock));
+
+	while (!is_send_done(tip) && tclock < oclock) {
+		dreal = oclock - tclock;
+		req.tv_sec = dreal / (1000 * 1000 * 1000);
+		req.tv_nsec = dreal % (1000 * 1000 * 1000);
+
+		if (verbose > 1) {
+			fprintf(tip->vfp, "++ stall(%lld.%09lld) ++\n",
+				(long long)req.tv_sec,
+				(long long)req.tv_nsec);
+		}
+
+		if (nanosleep(&req, NULL) < 0 && signal_done)
+			break;
+
+		tclock = gettime() - rgenesis;
+	}
+}
+
+/**
+ * iocbs_map - Map a set of AIOs onto a set of IOCBs
+ * @tip: Per-thread information
+ * @list: List of AIOs created
+ * @pkts: AIOs to map
+ * @ntodo: Number of AIOs to map
+ */
+static void iocbs_map(struct thr_info *tip, struct iocb **list, 
+					     struct io_pkt *pkts, int ntodo)
+{
+	int i;
+	struct io_pkt *pkt;
+
+	assert(0 < ntodo && ntodo <= naios);
+
+	pthread_mutex_lock(&tip->mutex);
+	assert(ntodo <= list_len(&tip->free_iocbs));
+	for (i = 0, pkt = pkts; i < ntodo; i++, pkt++) {
+		__u32 rw = pkt->rw;
+		struct iocb_pkt *iocbp;
+
+		if (!pkt->rw && !write_enabled)
+			rw = 1;
+
+		if (verbose > 1)
+			fprintf(tip->vfp, "\t%10llu + %10llu %c%c\n",
+				(unsigned long long)pkt->sector, 
+				(unsigned long long)pkt->nbytes / nb_sec,
+				rw ? 'R' : 'W', 
+				(rw == 1 && pkt->rw == 0) ? '!' : ' ');
+		
+		iocbp = list_entry(tip->free_iocbs.next, struct iocb_pkt, head);
+		iocb_setup(iocbp, rw, pkt->nbytes, pkt->sector * nb_sec);
+
+		list_move_tail(&iocbp->head, &tip->used_iocbs);
+		list[i] = &iocbp->iocb;
+	}
+
+	tip->naios_free -= ntodo;
+	assert(tip->naios_free >= 0);
+	pthread_mutex_unlock(&tip->mutex);
+}
+
+/**
+ * process_bunch - Process a bunch of requests
+ * @tip: Per-thread information
+ * @bunch: Bunch to process
+ */
+static void process_bunch(struct thr_info *tip, struct io_bunch *bunch)
+{
+	__u64 i = 0;
+	struct iocb *list[bunch->hdr.npkts];
+
+	assert(0 < bunch->hdr.npkts && bunch->hdr.npkts <= BT_MAX_PKTS);
+	while (!is_send_done(tip) && (i < bunch->hdr.npkts)) {
+		long ndone;
+		int ntodo = min(nfree_current(tip), bunch->hdr.npkts - i);
+
+		assert(0 < ntodo && ntodo <= naios);
+		iocbs_map(tip, list, &bunch->pkts[i], ntodo);
+		if (!no_stalls)
+			stall(tip, bunch->hdr.time_stamp - genesis);
+
+		if (ntodo) {
+			if (verbose > 1)
+				fprintf(tip->vfp, "submit(%d)\n", ntodo);
+			ndone = io_submit(tip->ctx, ntodo, list);
+			if (ndone != (long)ntodo) {
+				fatal("io_submit", ERR_SYSCALL,
+					"%d: io_submit(%d:%ld) failed (%s)\n", 
+					tip->cpu, ntodo, ndone, 
+					strerror(labs(ndone)));
+				/*NOTREACHED*/
+			}
+
+			pthread_mutex_lock(&tip->mutex);
+			tip->naios_out += ndone;
+			assert(tip->naios_out <= naios);
+			if (tip->reap_wait) {
+				tip->reap_wait = 0;
+				pthread_cond_signal(&tip->cond);
+			}
+			pthread_mutex_unlock(&tip->mutex);
+
+			i += ndone;
+			assert(i <= bunch->hdr.npkts);
+		}
+	}
+}
+
+/**
+ * reset_input_file - Reset the input file for the next iteration
+ * @tip: Thread information
+ *
+ * We also do a dummy read of the file header to get us to the first bunch.
+ */
+static void reset_input_file(struct thr_info *tip)
+{
+	struct io_file_hdr hdr;
+
+	lseek(tip->ifd, 0, 0);
+
+	if (read(tip->ifd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+		fatal(tip->file_name, ERR_ARGS, "Header reread failed\n");
+		/*NOTREACHED*/
+	}
+}
+
+/**
+ * replay_sub - Worker thread to submit AIOs that are being replayed
+ */
+static void *replay_sub(void *arg)
+{
+        unsigned int i;
+	char *mdev;
+	char path[MAXPATHLEN];
+	struct io_bunch bunch;
+	struct thr_info *tip = arg;
+	int oflags;
+
+	pin_to_cpu(tip);
+
+	mdev = map_dev(tip->devnm);
+	sprintf(path, "/dev/%s", mdev);
+	/*
+	 * convert underscores to slashes to
+	 * restore device names that have larger paths
+	 */
+	for (i = 0; i < strlen(mdev); i++)
+	        if (path[strlen("/dev/") + i] == '_')
+		        path[strlen("/dev/") + i] = '/';
+#ifdef O_NOATIME
+	oflags = O_NOATIME;
+#else
+	oflags = 0;
+#endif
+	tip->ofd = open(path, O_RDWR | O_DIRECT | oflags);
+	if (tip->ofd < 0) {
+		fatal(path, ERR_SYSCALL, "Failed device open\n");
+		/*NOTREACHED*/
+	}
+
+	set_replay_ready();
+	while (!is_send_done(tip) && tip->iterations--) {
+		wait_iter_start();
+		if (verbose > 1)
+			fprintf(tip->vfp, "\n=== %d ===\n", tip->iterations);
+		while (!is_send_done(tip) && next_bunch(tip, &bunch))
+			process_bunch(tip, &bunch);
+		set_iter_done();
+		reset_input_file(tip);
+	}
+	tip->send_done = 1;
+	set_replay_done();
+
+	return NULL;
+}
+
+/* 
+ * ========================================================================
+ * ==== COMMAND LINE ARGUMENT HANDLING ====================================
+ * ========================================================================
+ */
+
+static char usage_str[] = 						\
+        "\n"								\
+        "\t[ -c <cpus> : --cpus=<cpus>           ] Default: 1\n"        \
+        "\t[ -d <dir>  : --input-directory=<dir> ] Default: .\n"        \
+	"\t[ -F        : --find-records          ] Default: Off\n"	\
+        "\t[ -h        : --help                  ] Default: Off\n"      \
+        "\t[ -i <base> : --input-base=<base>     ] Default: replay\n"   \
+        "\t[ -I <iters>: --iterations=<iters>    ] Default: 1\n"        \
+        "\t[ -M <file> : --map-devs=<file>       ] Default: None\n"     \
+        "\t[ -N        : --no-stalls             ] Default: Off\n"      \
+        "\t[ -x        : --acc-factor            ] Default: 1\n"	\
+        "\t[ -v        : --verbose               ] Default: Off\n"      \
+        "\t[ -V        : --version               ] Default: Off\n"      \
+        "\t[ -W        : --write-enable          ] Default: Off\n"      \
+        "\t<dev...>                                Default: None\n"     \
+        "\n";
+
+#define S_OPTS	"c:d:Fhi:I:M:Nx:t:vVW"
+static struct option l_opts[] = {
+	{
+		.name = "cpus",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'c'
+	},
+	{
+		.name = "input-directory",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'd'
+	},
+	{
+		.name = "find-records",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'F'
+	},
+	{
+		.name = "help",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'h'
+	},
+	{
+		.name = "input-base",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'i'
+	},
+	{
+		.name = "iterations",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'I'
+	},
+	{
+		.name = "map-devs",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'M'
+	},
+	{
+		.name = "no-stalls",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'N'
+	},
+	{
+		.name = "acc-factor",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'x'
+	},
+	{
+		.name = "verbose",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'v'
+	},
+	{
+		.name = "version",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'V'
+	},
+	{
+		.name = "write-enable",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'W'
+	},
+	{
+		.name = NULL
+	}
+};
+
+/**
+ * handle_args: Parse passed in argument list
+ * @argc: Number of arguments in argv
+ * @argv: Arguments passed in
+ *
+ * Does rudimentary parameter verification as well.
+ */
+static void handle_args(int argc, char *argv[])
+{
+	int c;
+	int r;
+
+	while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
+		switch (c) {
+		case 'c': 
+			cpus_to_use = atoi(optarg);
+			if (cpus_to_use <= 0 || cpus_to_use > ncpus) {
+				fatal(NULL, ERR_ARGS, 
+				      "Invalid number of cpus %d (0<x<%d)\n",
+				      cpus_to_use, ncpus);
+				/*NOTREACHED*/
+			}
+			break;
+
+		case 'd':
+			idir = optarg;
+			if (access(idir, R_OK | X_OK) != 0) {
+				fatal(idir, ERR_ARGS, 
+				      "Invalid input directory specified\n");
+				/*NOTREACHED*/
+			}
+			break;
+
+		case 'F': 
+			find_records = 1;
+			break;
+
+		case 'h': 
+			usage(); 
+			exit(0);
+			/*NOTREACHED*/
+
+		case 'i': 
+			ibase = optarg;
+			break;
+
+		case 'I':
+			def_iterations = atoi(optarg);
+			if (def_iterations <= 0) {
+				fprintf(stderr, 
+					"Invalid number of iterations %d\n",
+					def_iterations);
+				exit(ERR_ARGS);
+				/*NOTREACHED*/
+			}
+			break;
+
+		case 'M':
+			read_map_devs(optarg);
+			break;
+
+		case 'N':
+			no_stalls = 1;
+			break;
+
+		case 'x':
+			r = sscanf(optarg,"%u",&acc_factor);
+			if (r!=1) {
+				fprintf(stderr,
+					"Invalid acceleration factor\n");
+				exit(ERR_ARGS);
+				/*NOTREACHED*/
+			}
+			break;
+
+		case 'V':
+			fprintf(stderr, "btreplay -- version %s\n", 
+				my_btversion);
+			fprintf(stderr, "            Built on %s\n", 
+				build_date);
+			exit(0);
+			/*NOTREACHED*/
+
+		case 'v':
+			verbose++;
+			break;
+
+		case 'W':
+			write_enabled = 1;
+			break;
+
+		default:
+			usage();
+			fatal(NULL, ERR_ARGS, 
+			      "Invalid command line argument %c\n", c);
+			/*NOTREACHED*/
+		}
+	}
+
+	while (optind < argc)
+		add_input_dev(argv[optind++]);
+
+	if (find_records)
+		find_input_devs(idir);
+
+	if (list_len(&input_devs) == 0) {
+		fatal(NULL, ERR_ARGS, "Missing required input dev name(s)\n");
+		/*NOTREACHED*/
+	}
+
+	if (cpus_to_use < 0)
+		cpus_to_use = ncpus;
+}
+
+/* 
+ * ========================================================================
+ * ==== MAIN ROUTINE ======================================================
+ * ========================================================================
+ */
+
+/**
+ * set_signal_done - Signal handler, catches signals & sets signal_done
+ */
+static void set_signal_done(__attribute__((__unused__))int signum)
+{
+	signal_done = 1;
+}
+
+/**
+ * main - 
+ * @argc: Number of arguments
+ * @argv: Array of arguments
+ */
+int main(int argc, char *argv[])
+{
+	int i;
+	struct list_head *p;
+
+	pgsize = getpagesize();
+	assert(pgsize > 0);
+
+	setup_signal(SIGINT, set_signal_done);
+	setup_signal(SIGTERM, set_signal_done);
+
+	get_ncpus();
+	handle_args(argc, argv);
+	find_input_files();
+
+	nfiles = list_len(&input_files);
+	__list_for_each(p, &input_files) {
+		tip_init(list_entry(p, struct thr_info, head));
+	}
+
+	wait_replays_ready();
+	for (i = 0; i < def_iterations; i++) {
+		rgenesis = gettime();
+		start_iter();
+		if (verbose)
+			fprintf(stderr, "I");
+		wait_iters_done();
+	}
+
+	wait_replays_done();
+	wait_reclaims_done();
+
+	if (verbose)
+		fprintf(stderr, "\n");
+
+	rem_input_files();
+	release_map_devs();
+
+	return 0;
+}
diff --git a/btreplay/doc/Makefile b/btreplay/doc/Makefile
new file mode 100644
index 0000000..e3b383e
--- /dev/null
+++ b/btreplay/doc/Makefile
@@ -0,0 +1,18 @@
+DOCTMP  = btreplay.log btreplay.aux btreplay.dvi btreplay.toc
+
+all: btreplay.dvi btreplay.pdf
+
+btreplay.tex:
+	@touch btreplay.tex
+
+btreplay.dvi: btreplay.tex abstract.tex
+	@latex btreplay.tex
+	@latex btreplay.tex
+
+btreplay.pdf: btreplay.dvi
+	@dvipdfm -p letter btreplay
+
+clean:
+	-rm -f $(DOCTMP)
+	-rm -f *.bak *.ps *.pdf
+	@rm -rf btreplay
diff --git a/btreplay/doc/abstract.tex b/btreplay/doc/abstract.tex
new file mode 100644
index 0000000..314d820
--- /dev/null
+++ b/btreplay/doc/abstract.tex
@@ -0,0 +1,34 @@
+%
+% Copyright (C) 2007 Alan D. Brunelle <Alan.Brunelle@hp.com>
+%
+%  This program is free software; you can redistribute it and/or modify
+%  it under the terms of the GNU General Public License as published by
+%  the Free Software Foundation; 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
+%
+%  vi :set textwidth=75
+%
+The \texttt{btrecord} and \texttt{btreplay} tools provide the ability to
+record and replay IOs captured by the \texttt{blktrace} utility. Attempts
+are made to maintain ordering, CPU mappings and time-separation of IOs. The
+general workflow is expected to be:
+
+\begin{enumerate}
+  \item Initiate \texttt{blktrace} to capture traces
+  \item Generate traces\ldots
+  \item Stop \texttt{blktrace}
+  \item Run \texttt{btrecord} to convert traces into IO records 
+  \item Utilize \texttt{btreplay} to replay IOs 
+\end{enumerate}
+
+This document will discuss the operating characteristics of
+\texttt{btreplay} and provide detailed command line option descriptions.
diff --git a/btreplay/doc/btreplay.tex b/btreplay/doc/btreplay.tex
new file mode 100644
index 0000000..8b0ecf7
--- /dev/null
+++ b/btreplay/doc/btreplay.tex
@@ -0,0 +1,532 @@
+%
+% Copyright (C) 2007 Alan D. Brunelle <Alan.Brunelle@hp.com>
+%
+%  This program is free software; you can redistribute it and/or modify
+%  it under the terms of the GNU General Public License as published by
+%  the Free Software Foundation; 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
+%
+%  vi :set textwidth=75
+%
+\documentclass{article}
+\usepackage{multirow,graphicx,placeins}
+
+\begin{document}
+%---------------------
+\title{\texttt{btrecord} and \texttt{btreplay} User Guide}
+\author{Alan D. Brunelle (Alan.Brunelle@hp.com)}
+\date{\today}
+\maketitle
+\begin{abstract}
+\input{abstract.tex}
+\end{abstract}
+\thispagestyle{empty}\newpage
+%---------------------
+\tableofcontents\thispagestyle{empty}\newpage
+%---------------------
+\section{Introduction}
+\input{abstract.tex}
+
+\bigskip 
+This document presents the command line overview for
+\texttt{btrecord} and \texttt{btreplay}, and shows some commonly used
+example usages of it in everyday work here at OSLO's Scalability and
+Performance Group.
+
+\subsection*{Build Note}
+
+To build these tools, one needs to
+place the source directory next to a valid
+\texttt{blktrace}\footnote{\texttt{git://git.kernel.dk/blktrace.git}}
+directory, as it includes \texttt{../blktrace} in the \texttt{Makefile}.
+
+
+%---------------------
+\newpage\section{\texttt{btrecord} and \texttt{btreplay} Operating Model}
+
+The \texttt{blktrace} utility provides the ability to collect detailed
+traces from the kernel for each IO processed by the block IO layer. The
+traces provide a complete timeline for each IO processed, including
+detailed information concerning when an IO was first received by the block
+IO layer -- indicating the device, CPU number, time stamp, IO direction,
+sector number and IO size (number of sectors). Using this information,
+one is able to \emph{replay} the IO again on the same machine or another
+set up entirely.
+
+\subsection{Basic Workflow}
+The basic operating work-flow to replay IOs would be something like:
+
+\begin{enumerate}
+  \item Run \texttt{blktrace} to collect traces. Here you specify the
+  device or devices that you wish to trace and later replay IOs upon. Note:
+  the only traces you are interested in are \emph{QUEUE} requests --
+  thus, to save system resources (including storage for traces), one could
+  specify the \texttt{-a queue} command line option to \texttt{blktrace}.
+
+  \item While \texttt{blktrace} is running, you run the workload that you
+  are interested in. 
+
+  \item When the work load has completed, you stop the \texttt{blktrace}
+  utility (thus saving all traces over the complete workload). 
+
+  \item You extract the pertinent IO information from the traces saved by
+  \texttt{blktrace} using the \texttt{btrecord} utility. This will parse
+  each trace file created by \texttt{blktrace}, and craft IO descriptions
+  to be used in the next phase of the workload processing.
+
+  \item Once \texttt{btrecord} has successfully created a series of data
+  files to be processed, you can run the \texttt{btreplay} utility which
+  attempts to generate the same IOs seen during the sample workload phase.
+\end{enumerate}
+
+\subsection{IO Stream Replay Characteristics}
+  The major characteristics of the IO stream that are kept intact include:
+
+  \begin{description}
+    \item[Device] The IOs are replayed on the same device as was seen
+    during the sample workload.
+
+    \item[IO direction] The same IO direction (read/write) is maintained.
+
+    \item[IO offset] The same device offset is maintained.
+
+    \item[IO size] The same number of sectors are transferred.
+
+    \item[Time differential] The time stamps stored during the
+    \texttt{blktrace} run are used to determine the amount of time between
+    IOs during the sample workload. \texttt{btreplay} \emph{attempts} to
+    maintain the same time differential between IOs, but no guarantees as
+    to complete accuracy are provided by the utility.
+
+    \item[Device IO Stream Ordering] All IOs on a device are submitted in
+    the precise order they were seen during the sample workload run. 
+  \end{description}
+
+  As noted above, the time between IOs may not be accurately maintained
+  during replays. In addition the actual ordering of IOs \emph{between}
+  devices is not necessarily maintained. (Each device with an IO stream
+  maintains its own concept of time, and thus there may be slippage of the
+  time kept between managing threads.)
+
+  \begin{quotation}
+    We have prototyped a different approach, wherein a single managing
+    thread handles all IOs across all devices. This approach, while
+    guaranteeing correct ordering of IOs across all devices, resulted in
+    much worse timing on a per IO basis. 
+  \end{quotation}
+
+\subsection{\texttt{btrecord/btreplay} Method of Operation}
+
+As noted above, \texttt{btrecord} extracts \texttt{QUEUE} operations from
+\texttt{blktrace} output. These \texttt{QUEUE} operations indicate the
+entrance of IOs into the block IO layer. In order to replay these IOs with
+some accuracy in regards to ordering and timeliness, we decided to take
+multiple sequential (in time) IOs and put them in a single \emph{bunch} of
+IOs that will be processed as a single \emph{asynchronous IO} call to the
+kernel\footnote{Attempts to do them individually resulted in too large of a
+turnaround time penalty (user-space to kernel and back). Note that in a
+number of workloads, the IOs are coming in from the page cache handling
+code, and thus are submitted to the block IO layer with \emph{very small}
+time intervals between issues.}. To manage the size of the \emph{bunches},
+the \texttt{btrecord} utility provides you with two controlling knobs:
+
+\begin{description}
+  \item[\texttt{--max-bunch-time}] This is the amount of time to encompass
+  in one bunch -- only IOs within the time specified are eligible
+  for \emph{bunching.} The default time is 10 milliseconds (10,000,000
+  nanoseconds). Refer to section~\ref{sec:c-o-m} on page~\pageref{sec:c-o-m}
+  for more information.
+
+  \item[\texttt{--max-pkts}] A \emph{bunch} size can be anywhere from
+  1 to 512 packets in size and by default we max a bunch to contain no
+  more than 8 individual IOs. With this option, one can increase or
+  decrease the maximum \emph{bunch} size.  Refer to section~\ref{sec:c-o-M}
+  on page~\pageref{sec:c-o-M} for more information.
+\end{description}
+
+Each input data file (one per device per CPU) results in a new record
+data file (again, one per device per CPU) which contains information
+about \emph{bunches} of IOs to be replayed. \texttt{btreplay} operates on
+these record data files by spawning a new pair of threads per file. One
+thread manages the submitting of AIOs per bunch in the record data file,
+while the other thread manages reclaiming AIOs completed\footnote{We
+have found that having the same thread do both results in a further
+reduction in replay timing accuracy.}.
+
+Each submitting thread simply reads the input file of \emph{bunches}
+recorded by \texttt{btrecord}, and attempts to faithfully reproduce the
+ordering and timing of IOs seen during the sample workload. The reclaiming
+thread simply waits for AIO completions, freeing up resources for the
+submitting thread to utilize to submit new AIOs.
+
+The number of CPUs being used on the replay system can be different from
+the number on the recorded system. To help with mappings here the
+\texttt{--cpus} option allows one to state how many CPUs on the replay
+system to utilize. If the number of CPUs on the replay system is less than
+on the recording system, we wrap CPU IDs. This \emph{may} result in an
+overload of CPU processing capabilities on the replay system. (Refer to
+section~\ref{sec:p-o-c} on page~\pageref{sec:p-o-c} for more details about the
+\texttt{--cpus} option.)
+
+\newpage\subsection{Known Deficiencies and Proposed Possible Fixes}
+
+The overall known deficiencies with this current set of utilities is
+outlined here, in some cases ideas on additions and/or improvements are
+included as well.
+
+\begin{enumerate}
+  \item Lack of IO ordering across devices. 
+
+  \begin{quote}
+    \emph{We could institute the notion of global time across threads,
+    and thus ensure IO ordering across devices, with some reduction in
+    timing accuracy.}
+  \end{quote}
+
+  \item Lack of IO timing accuracy -- additional time between IO bunches.
+
+  \begin{quote}
+    \emph{This is the primary problem with any IO replay mechanism -- how
+    to guarantee per-IO timing accuracy with respect to other replayed IOs?
+    One idea to reduce errors in this area would be to push the IO replay
+    into the kernel, where you \emph{may} receive more responsive timings.}
+  \end{quote}
+
+  \item Bunching of IOs results in reduced time amongst IOs within a bunch.
+
+  \begin{quote}
+    \emph{The user has \emph{some} control over this (via the
+    \texttt{--max-pkts} option). One \emph{could} simply specify
+    \texttt{-max-pkts=1} and then each IO would be treated individually. Of
+    course, this would probably then run into the problem of excessive
+    inter-IO times.}
+  \end{quote}
+
+  \item 1-to-1 mapping of devices -- for now the devices on the replay
+  machine must be the same as on the recording machine. 
+
+  \begin{quote}
+    \emph{It should be relatively trivial to add in the notion of
+    mapping -- simply include a file that is read which maps devices
+    on one machine to devices (with offsets and sizes) on the replay
+    machine\footnote{The notion of an offset and device size to replay on
+    could be used to both allow for a single device to masquerade as more
+    than one device, and could be utilized in case the replay device is
+    smaller than the recorded device.}.}
+    
+    \medskip\emph{One could also add in the notion of CPU mappings as well --
+    device $D_{rec}$ managed by CPU $C_{rec}$ on the recorded system
+    shall be replayed on device $D_{rep}$ and CPU $C_{rep}$ on the
+    replay machine.}
+
+    \bigskip
+    \begin{quote}
+      With version 0.9.1 we now support the \texttt{-M} option to do this
+      -- see section~\ref{sec:p-o-M} on page~\pageref{sec:p-o-M} for more
+      information on device mapping.
+    \end{quote}
+  \end{quote}
+
+\end{enumerate}
+
+%---------------------
+\newpage\section{\label{sec:command-line}Command Line Options}
+\subsection{\texttt{btrecord} Command Line Options}
+\begin{figure}[h!]
+\begin{verbatim}
+Usage: btrecord -- version 0.9.3
+
+	[ -d <dir>  : --input-directory=<dir> ] Default: .
+	[ -D <dir>  : --output-directory=<dir>] Default: .
+	[ -F        : --find-traces           ] Default: Off
+	[ -h        : --help                  ] Default: Off
+	[ -m <nsec> : --max-bunch-time=<nsec> ] Default: 10 msec
+	[ -M <pkts> : --max-pkts=<pkts>       ] Default: 8
+	[ -o <base> : --output-base=<base>    ] Default: replay
+	[ -v        : --verbose               ] Default: Off
+	[ -V        : --version               ] Default: Off
+	<dev>...                                Default: None
+\end{verbatim}
+\caption{\label{fig:btrecord--help}\texttt{btrecord --help} Output}
+\end{figure}
+\FloatBarrier
+
+\subsubsection{\label{sec:c-o-d}\texttt{-d} or
+\texttt{--input-directory}\\Set Input Directory}
+
+The \texttt{-d} option requires a single parameter providing the directory
+name for where input files are to be found. The default directory is the
+current directory (\texttt{.}).
+
+\subsubsection{\label{sec:c-o-D}\texttt{-D} or
+\texttt{--output-directory}\\Set Output Directory}
+
+The \texttt{-D} option requires a single parameter providing the directory
+name for where output files are to be placed. The default directory is the
+current directory (\texttt{.}).
+
+\subsubsection{\texttt{-F} or \texttt{--find-traces}\\Find Trace Files
+Automatically}
+
+The \texttt{-F} option instructs \texttt{btrecord} to go find all the
+trace files in the directory specified (either via the \texttt{-d}
+option, or in the default directory '.').
+
+\subsubsection{\texttt{-h} or \texttt{--help}\\Display Help Message}
+\subsubsection{\texttt{-V} or \texttt{--version}\\Display
+\texttt{btrecord}Version}
+
+The \texttt{-h} option displays the command line options and
+defaults, as presented in figure~\ref{fig:btrecord--help} on
+page~\pageref{fig:btrecord--help}.
+
+The \texttt{-V} option displays the \texttt{btreplay} version, as shown here:
+
+\begin{verbatim}
+$ btrecord --version
+btrecord -- version 0.9.0
+\end{verbatim}
+
+Both commands exit immediately after processing the option.
+
+\subsubsection{\label{sec:c-o-m}\texttt{-m} or
+\texttt{--max-bunch-time}\\Set Maximum Time Per Bunch}
+
+The \texttt{-m} option requires a single parameter which specifies an
+amount of time (in nanoseconds) to include in any one bunch of IOs that
+are to be processed. The smaller the value, the smaller the number of
+IOs processed at one time -- perhaps yielding in more realistic replay.
+However, after a certain point the amount of overhead per bunch may result
+in additional real replay time, thus yielding less accurate replay times.
+
+The default value is 10,000,000 nanoseconds (10 milliseconds).
+
+\subsubsection{\label{sec:c-o-M}\texttt{-M} or
+\texttt{--max-pkts}\\Set Maximum Packets Per Bunch}
+
+The \texttt{-M} option requires a single parameter which specifies the
+maximum number of IOs to store in a single bunch. As with the \texttt{-m}
+option (section~\ref{sec:c-o-m}), smaller values \emph{may} or \emph{may not}
+yield more accurate replay times.
+
+The default value is 8, with a maximum value of up to 512 being supported.
+
+\subsubsection{\label{sec:c-o-o}\texttt{-o} or
+\texttt{--output-base}\\Set Base Name for Output Files}
+
+Each output file has 3 fields:
+
+\begin{enumerate}
+  \item Device identifier (taken directly from the device name of the
+  \texttt{blktrace} output file).
+
+  \item \texttt{btrecord} base name -- by default ``replay''.
+
+  \item And the CPU number (again, taken directly from the
+  \texttt{blktrace} output file name).
+\end{enumerate}
+
+This option requires a single parameter that will override the default name
+(replay), and replace it with the specified value.
+
+\subsubsection{\label{sec:c-o-v}\texttt{-v} or
+\texttt{--verbose}\\Select Verbose Output}
+
+This option will output some simple statistics at the end of a successful
+run. Figure~\ref{fig:verb-out} (page~\pageref{fig:verb-out}) shows
+an example of some output, while figure~\ref{fig:verb-defs}
+(page~\pageref{fig:verb-defs}) shows what the fields mean.
+
+\begin{figure}[h!]
+\begin{verbatim}
+sdab:0: 580661 pkts (tot), 126030 pkts (replay), 89809 bunches, 1.4 pkts/bunch
+sdab:1: 2559775 pkts (tot), 430172 pkts (replay), 293029 bunches, 1.5 pkts/bunch
+sdab:2: 653559 pkts (tot), 136522 pkts (replay), 102288 bunches, 1.3 pkts/bunch
+sdab:3: 474773 pkts (tot), 117849 pkts (replay), 69572 bunches, 1.7 pkts/bunch
+\end{verbatim}
+\caption{\label{fig:verb-out}Verbose Output Example}
+\end{figure}
+\FloatBarrier
+
+\begin{figure}[h!]
+\begin{description}
+  \item[Field 1] The first field contains the device name and CPU
+  identifier. Thus: \texttt{sdab:0:} means the device \texttt{sdab} and
+  traces on CPU 0. 
+
+  \item[Field 2] The second field contains the total number of packets
+  processed for each device file. 
+
+  \item[Field 3] The next field shows the number of packets eligible for
+  replay. 
+
+  \item[Field 4] The fourth field contains the total number of IO bunches. 
+
+  \item[Field 5] The last field shows the average number of IOs per bunch
+  recorded.
+\end{description}
+\caption{\label{fig:verb-defs}Verbose Field Definitions}
+\end{figure}
+\FloatBarrier
+
+%---------------------
+\newpage\subsection{\texttt{btreplay} Command Line Options}
+\begin{figure}[h!]
+\begin{verbatim}
+Usage: btreplay -- version 0.9.3
+
+	[ -c <cpus> : --cpus=<cpus>           ] Default: 1
+	[ -d <dir>  : --input-directory=<dir> ] Default: .
+	[ -F        : --find-records          ] Default: Off
+	[ -h        : --help                  ] Default: Off
+	[ -i <base> : --input-base=<base>     ] Default: replay
+	[ -I <iters>: --iterations=<iters>    ] Default: 1
+	[ -M <file> : --map-devs=<file>       ] Default: None
+	[ -N        : --no-stalls             ] Default: Off
+	[ -x <int>  : --acc-factor=<int>      ] Default: 1
+	[ -v        : --verbose               ] Default: Off
+	[ -V        : --version               ] Default: Off
+	[ -W        : --write-enable          ] Default: Off
+	<dev...>                                Default: None
+\end{verbatim}
+\caption{\label{fig:btreplay--help}\texttt{btreplay --help} Output}
+\end{figure}
+\FloatBarrier
+
+\subsubsection{\label{sec:p-o-c}\texttt{-c} or
+\texttt{--cpus}\\Set Number of CPUs to Use}
+
+\subsubsection{\label{sec:p-o-d}\texttt{-d} or
+\texttt{--input-directory}\\Set Input Directory}
+
+The \texttt{-d} option requires a single parameter providing the directory
+name for where input files are to be found. The default directory is the
+current directory (\texttt{.}).
+
+\subsubsection{\texttt{-F} or \texttt{--find-records}\\Find RecordFiles
+Automatically}
+
+The \texttt{-F} option instructs \texttt{btreplay} to go find all the
+record files in the directory specified (either via the \texttt{-d}
+option, or in the default directory '.').
+
+\subsubsection{\texttt{-h} or \texttt{--help}\\Display Help Message}
+\subsubsection{\texttt{-V} or \texttt{--version}\\Display
+\texttt{btreplay}Version}
+
+The \texttt{-h} option displays the command line options and
+defaults, as presented in figure~\ref{fig:btreplay--help} on
+page~\pageref{fig:btreplay--help}.
+
+The \texttt{-V} option displays the \texttt{btreplay} version, as show here:
+
+\begin{verbatim}
+$ btreplay --version
+btreplay -- version 0.9.0
+\end{verbatim}
+
+Both commands exit immediately after processing the option.
+
+\subsubsection{\label{sec:p-o-i}\texttt{-i} or
+\texttt{--input-base}\\Set Base Name for Input Files}
+
+Each input file has 3 fields:
+
+\begin{enumerate}
+  \item Device identifier (taken directly from the device name of the
+  \texttt{blktrace} output file).
+
+  \item \texttt{btrecord} base name -- by default ``replay''.
+
+  \item And the CPU number (again, taken directly from the
+  \texttt{blktrace} output file name).
+\end{enumerate}
+
+This option requires a single parameter that will override the default name
+(replay), and replace it with the specified value.
+
+\subsubsection{\label{sec:p-o-I}\texttt{-I} or
+\texttt{--iterations}\\Set Number of Iterations to Run}
+
+This option requires a single parameter which specifies the number of times
+to run through the input files. The default value is 1.
+
+\subsubsection{\label{sec:p-o-M}\texttt{-M} or \texttt{map-devs}\\
+Specify Device Mappings}
+
+This option requires a single parameter which specifies the name of a
+file containing device mappings. The file must be very simply managed, with
+just two pieces of data per line:
+
+\begin{enumerate}
+  \item The device name on the recorded system (with the \texttt{'/dev/'}
+  removed). Example: \texttt{/dev/sda} would just be \texttt{sda}.
+
+  \item The device name on the replay system to use (again, without the
+  \texttt{'/dev/'} path prepended).
+\end{enumerate}
+
+An example file for when one would map devices \texttt{/dev/sda} and
+\texttt{/dev/sdb} on the recorded system to \texttt{dev/sdg} and
+\texttt{sdh} on the replay system would be:
+
+\begin{verbatim}
+sda sdg
+sdb sdh
+\end{verbatim}
+
+The only entries in the file that are allowed are these two element lines
+-- we do not (yet?) support the notion of blank lines, or comment lines, or
+the like.
+
+The utility \emph{does} allow for multiple \texttt{-M} options to be
+supplied on the command line.
+
+\subsubsection{\label{sec:o-N}\texttt{-N} or \texttt{--no-stalls}\\Disable
+Pre-bunch Stalls}
+
+When specified on the command line, all pre-bunch stall indicators will be
+ignored. IOs will be replayed without inter-bunch delays.
+
+\subsubsection{\label{sec:o-x}\texttt{-x} or \texttt{--acc-factor}\\Acceleration
+Factor}
+
+  While the \texttt{--no-stalls} option allows the traces to be replayed
+  with no waiting time, this option specifies some acceleration factor
+  to be used. If the value of two is used, then the stall time is
+  divided by half resulting in a reduction of the execution time by
+  this factor. Note that if this number is too high, the results will
+  be equivalent of not having stall.
+
+\subsubsection{\label{sec:p-o-v}\texttt{-v} or
+\texttt{--verbose}\\Select Verbose Output}
+
+When specified on the command line, this option instructs \texttt{btreplay}
+to store information concerning each \emph{stall} and IO operation
+performed by \texttt{btreplay}. The name of each file so created will be
+the input file name used with an extension of \texttt{.rep} appended onto
+it. Thus, an input file of the name \texttt{sdab.replay.3} would generate a
+verbose output file with the name \texttt{sdab.replay.3.rep} in the
+directory specified for input files.
+
+In addition, \texttt{btreplay} will also output to \texttt{stderr} the
+names of the input files being processed.
+
+\subsubsection{\label{sec:p-o-W}\texttt{-W} or
+\texttt{--write-enable}\\Enable Writing During Replay}
+
+As a precautionary measure, by default \texttt{btreplay} will \emph{not}
+process \emph{write} requests. In order to enable \texttt{btreplay} to
+actually \emph{write} to devices one must explicitly specify the
+\texttt{-W} option.
+
+\end{document}
diff --git a/btt/Makefile b/btt/Makefile
new file mode 100644
index 0000000..df7a3de
--- /dev/null
+++ b/btt/Makefile
@@ -0,0 +1,45 @@
+#
+# OCFLAGS:
+# 	COUNT_IOS	- Counts struct io's left at end
+# 	DEBUG		- Various and sundy debug asserts
+# 	NDEBUG		- Defined: no asserts, Undefined: asserts
+#
+
+CC	= gcc
+CFLAGS	= -Wall -W -O2 -g
+INCS	= -I. -I..
+XCFLAGS	= -D_GNU_SOURCE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
+override CFLAGS += $(INCS) $(XCFLAGS)
+
+PROGS	= btt
+LIBS	= $(PLIBS) $(ELIBS)
+OBJS	= args.o bt_timeline.o devmap.o devs.o dip_rb.o iostat.o latency.o \
+	  misc.o output.o proc.o seek.o trace.o trace_complete.o trace_im.o \
+	  trace_issue.o trace_queue.o trace_remap.o trace_requeue.o \
+	  ../rbtree.o mmap.o trace_plug.o bno_dump.o unplug_hist.o q2d.o \
+	  aqd.o plat.o rstats.o p_live.o
+
+all: depend $(PROGS)
+
+.PHONY : depend
+depend: $(patsubst %.o,%.c,$(filter %.o,$(OBJS)))
+	@$(CC) -MM $(CFLAGS) -I.. $^ 1> .depend
+
+docs:
+	$(MAKE) -C doc all
+
+docsclean:
+	$(MAKE) -C doc clean
+
+clean: docsclean
+	-rm -f *.o $(PROGS) .depend
+
+%.o: %.c
+	$(CC) $(CFLAGS) -c -o $*.o $<
+
+btt: $(OBJS)
+	$(CC) $(CFLAGS) -o $@ $(filter %.o,$^) $(LIBS)
+
+ifneq ($(wildcard .depend),)
+include .depend
+endif
diff --git a/btt/README b/btt/README
new file mode 100644
index 0000000..c395a67
--- /dev/null
+++ b/btt/README
@@ -0,0 +1,17 @@
+blktrace Timeline
+-----------------
+
+Alan D. Brunelle (initial version)
+
+Please refer to the documentation for details.
+
+Resources
+---------
+
+vger hosts a mailing list dedicated to btrace discussion and development.
+The list is called linux-btrace@vger.kernel.org, subscribe by sending
+a mail to majordomo@vger.kernel.org with 'subscribe linux-btrace' in
+the mail body.
+
+2006-04-16, Alan D. Brunelle <Alan.Brunelle@hp.com>
+
diff --git a/btt/aqd.c b/btt/aqd.c
new file mode 100644
index 0000000..17ab15b
--- /dev/null
+++ b/btt/aqd.c
@@ -0,0 +1,82 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "globals.h"
+
+struct aqd_info {
+	FILE *fp;
+	int na;		/* # active */
+};
+
+void *aqd_alloc(struct d_info *dip)
+{
+	char *oname;
+	struct aqd_info *ap;
+
+	if (aqd_name == NULL) return NULL;
+
+	ap = malloc(sizeof(*ap));
+	ap->na = 0;
+
+	oname = malloc(strlen(aqd_name) + strlen(dip->dip_name) + 32);
+	sprintf(oname, "%s_%s_aqd.dat", aqd_name, dip->dip_name);
+	if ((ap->fp = my_fopen(oname, "w")) == NULL) {
+		perror(oname);
+		free(oname);
+		free(ap);
+		return NULL;
+	}
+	add_file(ap->fp, oname);
+
+	return ap;
+
+}
+
+void aqd_free(void *info)
+{
+	free(info);
+}
+
+void aqd_issue(void *info, double ts)
+{
+	if (info) {
+		struct aqd_info *ap = info;
+
+		fprintf(ap->fp, "%lf %d\n%lf %d\n", ts, ap->na, ts, ap->na + 1);
+		ap->na += 1;
+	}
+}
+
+void aqd_complete(void *info, double ts)
+{
+	if (info) {
+		struct aqd_info *ap = info;
+
+		if (ap->na > 0) {
+			fprintf(ap->fp, "%lf %d\n%lf %d\n",
+					ts, ap->na, ts, ap->na - 1);
+			ap->na -= 1;
+		}
+	}
+}
diff --git a/btt/args.c b/btt/args.c
new file mode 100644
index 0000000..5c5078a
--- /dev/null
+++ b/btt/args.c
@@ -0,0 +1,423 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "globals.h"
+
+#define SETBUFFER_SIZE	(64 * 1024)
+
+#define S_OPTS	"aAB:d:D:e:hi:I:l:L:m:M:o:p:P:q:Q:rs:S:t:T:u:VvXz:Z"
+static struct option l_opts[] = {
+	{
+		.name = "seek-absolute",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'a'
+	},
+	{
+		.name = "all-data",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'A'
+	},
+	{
+		.name = "dump-blocknos",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'B'
+	},
+	{
+		.name = "range-delta",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'd'
+	},
+	{
+		.name = "devices",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'D'
+	},
+	{
+		.name = "exes",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'e'
+	},
+	{
+		.name = "help",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'h'
+	},
+	{
+		.name = "input-file",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'i'
+	},
+	{
+		.name = "iostat",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'I'
+	},
+	{
+		.name = "d2c-latencies",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'l'
+	},
+	{
+		.name = "periodic-latencies",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'L'
+	},
+	{
+		.name = "seeks-per-second",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'm'
+	},
+	{
+		.name = "dev-maps",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'M'
+	},
+	{
+		.name = "output-file",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'o'
+	},
+	{
+		.name = "per-io-dump",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'p'
+	},
+	{
+		.name = "per-io-trees",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'P'
+	},
+	{
+		.name = "q2c-latencies",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'q'
+	},
+	{
+		.name = "active-queue-depth",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'Q'
+	},
+	{
+		.name = "no-remaps",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'r'
+	},
+	{
+		.name = "seeks",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 's'
+	},
+	{
+		.name = "iostat-interval",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'S'
+	},
+	{
+		.name = "time-start",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 't'
+	},
+	{
+		.name = "time-end",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'T'
+	},
+	{
+		.name = "unplug-hist",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'u'
+	},
+	{
+		.name = "version",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'V'
+	},
+	{
+		.name = "verbose",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'v'
+	},
+	{
+		.name = "do-active",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'z'
+	},
+	{
+		.name = "easy-parse-avgs",
+		.has_arg = no_argument,
+		.flag = NULL,
+		.val = 'X'
+	},
+	{
+		.name = "q2d-latencies",
+		.has_arg = required_argument,
+		.flag = NULL,
+		.val = 'z'
+	},
+	{
+		.name = NULL,
+	}
+};
+
+static char usage_str[] = \
+	"\n[ -a               | --seek-absolute ]\n" \
+	"[ -A               | --all-data ]\n" \
+	"[ -B <output name> | --dump-blocknos=<output name> ]\n" \
+	"[ -d <seconds>     | --range-delta=<seconds> ]\n" \
+	"[ -D <dev;...>     | --devices=<dev;...> ]\n" \
+	"[ -e <exe,...>     | --exes=<exe,...>  ]\n" \
+	"[ -h               | --help ]\n" \
+	"[ -i <input name>  | --input-file=<input name> ]\n" \
+	"[ -I <output name> | --iostat=<output name> ]\n" \
+	"[ -l <output name> | --d2c-latencies=<output name> ]\n" \
+	"[ -L <freq>        | --periodic-latencies=<freq> ]\n" \
+	"[ -m <output name> | --seeks-per-second=<output name> ]\n" \
+	"[ -M <dev map>     | --dev-maps=<dev map>\n" \
+	"[ -o <output name> | --output-file=<output name> ]\n" \
+	"[ -p <output name> | --per-io-dump=<output name> ]\n" \
+	"[ -P <output name> | --per-io-trees=<output name> ]\n" \
+	"[ -q <output name> | --q2c-latencies=<output name> ]\n" \
+	"[ -Q <output name> | --active-queue-depth=<output name> ]\n" \
+	"[ -r               | --no-remaps ]\n" \
+	"[ -s <output name> | --seeks=<output name> ]\n" \
+	"[ -S <interval>    | --iostat-interval=<interval> ]\n" \
+	"[ -t <sec>         | --time-start=<sec> ]\n" \
+	"[ -T <sec>         | --time-end=<sec> ]\n" \
+	"[ -u <output name> | --unplug-hist=<output name> ]\n" \
+	"[ -V               | --version ]\n" \
+	"[ -v               | --verbose ]\n" \
+	"[ -X               | --easy-parse-avgs ]\n" \
+	"[ -z <output name> | --q2d-latencies=<output name> ]\n" \
+	"[ -Z               | --do-active\n" \
+	"\n";
+
+static void usage(char *prog)
+{
+	fprintf(stderr, "Usage: %s %s", prog, usage_str);
+}
+
+static FILE *setup_ofile(char *fname)
+{
+	if (fname) {
+		char *buf;
+		FILE *ofp = my_fopen(fname, "w");
+
+		if (!ofp) {
+			perror(fname);
+			exit(1);
+		}
+
+		buf = malloc(SETBUFFER_SIZE);
+		setbuffer(ofp, buf, SETBUFFER_SIZE);
+
+		add_file(ofp, fname);
+		add_buf(buf);
+
+		return ofp;
+	}
+
+	return NULL;
+}
+
+static FILE *std_open(char *output_name, char *sfx, char *msg)
+{
+	FILE *fp;
+	char fname[strlen(output_name) + 32];
+
+	sprintf(fname, "%s.%s", output_name, sfx);
+	fp = my_fopen(fname, "w");
+	if (fp == NULL) {
+		perror(fname);
+		exit(1);
+	}
+	if (verbose)
+		printf("Sending %s to %s\n", msg, fname);
+
+	return fp;
+}
+
+void handle_args(int argc, char *argv[])
+{
+	int c;
+
+	while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
+		switch (c) {
+		case 'a':
+			seek_absolute = 1;
+			break;
+		case 'A':
+			output_all_data = 1;
+			break;
+		case 'B':
+			bno_dump_name = optarg;
+			break;
+		case 'd':
+			sscanf(optarg, "%lf", &range_delta);
+			break;
+		case 'D':
+			devices = optarg;
+			break;
+		case 'e':
+			exes = optarg;
+			break;
+		case 'h':
+			usage(argv[0]);
+			exit(0);
+		case 'i':
+			input_name = optarg;
+			break;
+		case 'l':
+			d2c_name = optarg;
+			break;
+		case 'L':
+			plat_freq = atof(optarg);
+			break;
+		case 'I':
+			iostat_name = strdup(optarg);
+			break;
+		case 'm':
+			sps_name = optarg;
+			break;
+		case 'M':
+			if (dev_map_read(optarg))
+				exit(1);
+			break;
+		case 'o':
+			output_name = optarg;
+			break;
+		case 'p':
+			per_io_name = strdup(optarg);
+			break;
+		case 'P':
+			per_io_trees = optarg;
+			break;
+		case 'q':
+			q2c_name = optarg;
+			break;
+		case 'Q':
+			aqd_name = optarg;
+			break;
+		case 'r':
+			ignore_remaps = 1;
+			break;
+		case 's':
+			seek_name = optarg;
+			break;
+		case 'S': {
+			unsigned int interval;
+
+			sscanf(optarg, "%u", &interval);
+			iostat_interval = (__u64)interval * 1000000000LL;
+			break;
+		}
+		case 't':
+			sscanf(optarg, "%lf", &t_astart);
+			time_bounded = 1;
+			break;
+		case 'T':
+			sscanf(optarg, "%lf", &t_aend);
+			time_bounded = 1;
+			break;
+		case 'u':
+			unplug_hist_name = optarg;
+			break;
+		case 'v':
+			verbose = 1;
+			break;
+		case 'V':
+			printf("%s version %s\n", argv[0], bt_timeline_version);
+			exit(0);
+		case 'X':
+			easy_parse_avgs++;
+			break;
+		case 'z':
+			q2d_name = optarg;
+			break;
+		case 'Z':
+			do_p_live = 1;
+			break;
+		default:
+			usage(argv[0]);
+			exit(1);
+		}
+	}
+
+	if (input_name == NULL) {
+		usage(argv[0]);
+		exit(1);
+	}
+
+	if (sps_name && !seek_name) {
+		fprintf(stderr, "FATAL: -m option requires -s options\n");
+		exit(1);
+	}
+
+	setup_ifile(input_name);
+
+	if (output_name == NULL) {
+		rngs_ofp = avgs_ofp = msgs_ofp = stdout;
+		easy_parse_avgs = 0;
+	} else {
+		rngs_ofp = std_open(output_name, "dat", "range data");
+		avgs_ofp = std_open(output_name, "avg", "stats data");
+		msgs_ofp = std_open(output_name, "msg", "K messages");
+		if (easy_parse_avgs) {
+			xavgs_ofp = std_open(output_name, "xvg",
+					     "EZ stats data");
+		}
+	}
+
+	iostat_ofp = setup_ofile(iostat_name);
+	per_io_ofp = setup_ofile(per_io_name);
+}
diff --git a/btt/bno_dump.c b/btt/bno_dump.c
new file mode 100644
index 0000000..00c9ac2
--- /dev/null
+++ b/btt/bno_dump.c
@@ -0,0 +1,79 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ *
+ */
+#include "globals.h"
+
+struct bno_dump {
+	FILE *rfp, *wfp, *cfp;
+};
+
+static FILE *bno_dump_open(struct d_info *dip, char rwc)
+{
+	FILE *fp;
+	char *oname;
+
+	oname = malloc(strlen(bno_dump_name) + strlen(dip->dip_name) + 32);
+	sprintf(oname, "%s_%s_%c.dat", bno_dump_name, dip->dip_name, rwc);
+	if ((fp = my_fopen(oname, "w")) == NULL) {
+		perror(oname);
+		free(oname);
+	} else
+		add_file(fp, oname);
+	return fp;
+}
+
+static inline void bno_dump_write(FILE *fp, struct io *iop)
+{
+	fprintf(fp, "%15.9lf %lld %lld\n", BIT_TIME(iop->t.time),
+		(long long)BIT_START(iop), (long long)BIT_END(iop));
+}
+
+void *bno_dump_alloc(struct d_info *dip)
+{
+	struct bno_dump *bdp;
+
+	if (bno_dump_name == NULL) return NULL;
+
+	bdp = malloc(sizeof(*bdp));
+	bdp->rfp = bno_dump_open(dip, 'r');
+	bdp->wfp = bno_dump_open(dip, 'w');
+	bdp->cfp = bno_dump_open(dip, 'c');
+
+	return bdp;
+}
+
+void bno_dump_free(void *param)
+{
+	free(param);
+}
+
+void bno_dump_add(void *handle, struct io *iop)
+{
+	struct bno_dump *bdp = handle;
+
+	if (bdp) {
+		FILE *fp = IOP_READ(iop) ? bdp->rfp : bdp->wfp;
+
+		if (fp)
+			bno_dump_write(fp, iop);
+		if (bdp->cfp)
+			bno_dump_write(bdp->cfp, iop);
+	}
+}
diff --git a/btt/bno_plot.py b/btt/bno_plot.py
new file mode 100644
index 0000000..aa92480
--- /dev/null
+++ b/btt/bno_plot.py
@@ -0,0 +1,126 @@
+#! /usr/bin/env python
+#
+# btt blkno plotting interface
+#
+#  (C) Copyright 2008 Hewlett-Packard Development Company, L.P.
+#
+#  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
+#
+"""
+bno_plot.py
+	[ -h | --help       ]
+	[ -K | --keys-below ]
+	[ -v | --verbose    ]
+	[ <file...>         ]
+
+Utilizes gnuplot to generate a 3D plot of the block number output
+from btt.  If no <files> are specified, it will utilize all files
+generated after btt was run with -B blknos (meaning: all files of the
+form blknos*[rw].dat).
+
+The -K option forces bno_plot.py to put the keys below the graph,
+typically all keys for input files are put in the upper right corner
+of the graph. If the number of devices exceed 10, then bno_plot.py will
+automatically push the keys under the graph.
+
+To exit the plotter, enter 'quit' or ^D at the 'gnuplot> ' prompt.
+"""
+
+import getopt, glob, os, sys, tempfile
+
+verbose	= 0
+cmds	= """
+set title 'btt Generated Block Accesses'
+set xlabel 'Time (secs)'
+set ylabel 'Block Number'
+set zlabel '# Blocks per IO'
+set grid
+"""
+
+
+#-----------------------------------------------------------------------------
+def parse_args(in_args):
+	global verbose
+
+	keys_below = False
+	s_opts = 'hKv'
+	l_opts = [ 'help', 'keys-below', 'verbose' ]
+
+	try:
+		(opts, args) = getopt.getopt(in_args, s_opts, l_opts)
+	except getopt.error, msg:
+		print >>sys.stderr, msg
+		print >>sys.stderr, __doc__
+		sys.exit(1)
+
+	for (o, a) in opts:
+		if o in ('-h', '--help'):
+			print __doc__
+			sys.exit(0)
+		elif o in ('-v', '--verbose'):
+			verbose += 1
+		elif o in ('-K', '--keys-below'):
+			keys_below = True
+
+	if len(args) > 0:	bnos = args
+	else:			bnos = glob.glob('blknos*[rw].dat')
+
+	return (bnos, keys_below)
+
+#-----------------------------------------------------------------------------
+if __name__ == '__main__':
+	(bnos, keys_below) = parse_args(sys.argv[1:])
+
+	if verbose:
+		print 'Using files:',
+		for bno in bnos: print bno,
+		if keys_below:	print '\nKeys are to be placed below graph'
+		else:		print ''
+
+	tmpdir = tempfile.mktemp()
+	os.mkdir(tmpdir)
+
+	plot_cmd = None
+	for f in bnos:
+		t = '%s/%s' % (tmpdir, f)
+
+		fo = open(t, 'w')
+		for line in open(f, 'r'):
+			fld = line.split(None)
+			print >>fo, fld[0], fld[1], int(fld[2])-int(fld[1])
+		fo.close()
+
+		t = t[t.rfind('/')+1:]
+		if plot_cmd == None: plot_cmd = "splot '%s'" % t
+		else:                plot_cmd = "%s,'%s'" % (plot_cmd, t)
+
+	fo = open('%s/plot.cmds' % tmpdir, 'w')
+	print >>fo, cmds
+	if len(bnos) > 10 or keys_below: print >>fo, 'set key below'
+	print >>fo, plot_cmd
+	fo.close()
+
+	pid = os.fork()
+	if pid == 0:
+		cmd = 'gnuplot %s/plot.cmds -' % tmpdir
+
+		if verbose: print 'Executing %s' % cmd
+
+		os.chdir(tmpdir)
+		os.system(cmd)
+		sys.exit(1)
+
+	os.waitpid(pid, 0)
+	os.system('/bin/rm -rf ' + tmpdir)
diff --git a/btt/bt_timeline.c b/btt/bt_timeline.c
new file mode 100644
index 0000000..295e5ee
--- /dev/null
+++ b/btt/bt_timeline.c
@@ -0,0 +1,131 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <time.h>
+#include "globals.h"
+
+char bt_timeline_version[] = "2.09";
+
+char *devices, *exes, *input_name, *output_name, *seek_name, *bno_dump_name;
+char *d2c_name, *q2c_name, *per_io_name, *unplug_hist_name;
+char *sps_name, *aqd_name, *q2d_name, *per_io_trees;
+FILE *rngs_ofp, *avgs_ofp, *xavgs_ofp, *per_io_ofp, *msgs_ofp;
+int verbose, done, time_bounded, output_all_data, seek_absolute;
+int easy_parse_avgs, ignore_remaps, do_p_live;
+double t_astart, t_aend, last_t_seen;
+unsigned long n_traces;
+struct avgs_info all_avgs;
+unsigned int n_devs;
+time_t genesis, last_vtrace;
+LIST_HEAD(all_devs);
+LIST_HEAD(all_procs);
+LIST_HEAD(all_ios);
+LIST_HEAD(free_ios);
+LIST_HEAD(free_bilinks);
+__u64 q_histo[N_HIST_BKTS], d_histo[N_HIST_BKTS];
+
+double plat_freq = 0.0;
+double range_delta = 0.1;
+__u64 last_q = (__u64)-1;
+
+struct region_info all_regions = {
+	.qranges = LIST_HEAD_INIT(all_regions.qranges),
+	.cranges = LIST_HEAD_INIT(all_regions.cranges),
+};
+
+int process(void);
+
+int main(int argc, char *argv[])
+{
+	handle_args(argc, argv);
+
+	init_dev_heads();
+	iostat_init();
+	if (!rstat_init())
+		return 1;
+
+	if (process() || output_avgs(avgs_ofp) || output_ranges(rngs_ofp))
+		return 1;
+
+	if (iostat_ofp) {
+		fprintf(iostat_ofp, "\n");
+		iostat_dump_stats(iostat_last_stamp, 1);
+	}
+
+	if (msgs_ofp != stdout)
+		fclose(msgs_ofp);
+	if (rngs_ofp != stdout)
+		fclose(rngs_ofp);
+	if (avgs_ofp != stdout)
+		fclose(avgs_ofp);
+	if (xavgs_ofp)
+		fclose(xavgs_ofp);
+
+	dip_cleanup();
+	dev_map_exit();
+	dip_exit();
+	rstat_exit();
+	pip_exit();
+	io_free_all();
+	region_exit(&all_regions);
+	p_live_exit();
+	clean_allocs();
+
+	return 0;
+}
+
+static inline double tv2dbl(struct timeval *tv)
+{
+	return (double)tv->tv_sec + (((double)tv->tv_usec) / (1000.0 * 1000.0));
+}
+
+int process(void)
+{
+	int ret = 0;
+	struct io *iop = io_alloc();
+	struct timeval tvs, tve;
+
+	genesis = last_vtrace = time(NULL);
+	gettimeofday(&tvs, NULL);
+	while (!done && next_trace(&iop->t, &iop->pdu)) {
+		add_trace(iop);
+		iop = io_alloc();
+	}
+
+	io_release(iop);
+	gettimeofday(&tve, NULL);
+
+	if (verbose) {
+		double tps, dt_input = tv2dbl(&tve) - tv2dbl(&tvs);
+
+		tps = (double)n_traces / dt_input;
+		printf("\r                                        "
+		       "                                        \r");
+		printf("%10lu traces @ %.1lf Ktps in %.6lf seconds\n",
+			n_traces, tps/1000.0,
+			dt_input);
+	}
+
+	return ret;
+}
diff --git a/btt/btt_plot.py b/btt/btt_plot.py
new file mode 100755
index 0000000..b81dad5
--- /dev/null
+++ b/btt/btt_plot.py
@@ -0,0 +1,466 @@
+#! /usr/bin/env python
+#
+# btt_plot.py: Generate matplotlib plots for BTT generate data files
+#
+#  (C) Copyright 2009 Hewlett-Packard Development Company, L.P.
+#
+#  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
+#
+
+"""
+btt_plot.py: Generate matplotlib plots for BTT generated data files
+
+Files handled:
+  AQD	- Average Queue Depth		Running average of queue depths
+
+  BNOS	- Block numbers accessed	Markers for each block
+
+  Q2D	- Queue to Issue latencies	Running averages
+  D2C	- Issue to Complete latencies	Running averages
+  Q2C	- Queue to Complete latencies	Running averages
+
+Usage:
+  btt_plot_aqd.py	equivalent to: btt_plot.py -t aqd	<type>=aqd
+  btt_plot_bnos.py	equivalent to: btt_plot.py -t bnos	<type>=bnos
+  btt_plot_q2d.py	equivalent to: btt_plot.py -t q2d	<type>=q2d
+  btt_plot_d2c.py	equivalent to: btt_plot.py -t d2c	<type>=d2c
+  btt_plot_q2c.py	equivalent to: btt_plot.py -t q2c	<type>=q2c
+
+Arguments:
+  [ -A          | --generate-all   ] Default: False
+  [ -L          | --no-legend      ] Default: Legend table produced
+  [ -o <file>   | --output=<file>  ] Default: <type>.png
+  [ -T <string> | --title=<string> ] Default: Based upon <type>
+  [ -v          | --verbose        ] Default: False
+  <data-files...>
+
+  The -A (--generate-all) argument is different: when this is specified,
+  an attempt is made to generate default plots for all 5 types (aqd, bnos,
+  q2d, d2c and q2c). It will find files with the appropriate suffix for
+  each type ('aqd.dat' for example). If such files are found, a plot for
+  that type will be made. The output file name will be the default for
+  each type. The -L (--no-legend) option will be obeyed for all plots,
+  but the -o (--output) and -T (--title) options will be ignored.
+"""
+
+__author__ = 'Alan D. Brunelle <alan.brunelle@hp.com>'
+
+#------------------------------------------------------------------------------
+
+import matplotlib
+matplotlib.use('Agg')
+import getopt, glob, os, sys
+import matplotlib.pyplot as plt
+
+plot_size	= [10.9, 8.4]	# inches...
+
+add_legend	= True
+generate_all	= False
+output_file	= None
+title_str	= None
+type		= None
+verbose		= False
+
+types		= [ 'aqd', 'q2d', 'd2c', 'q2c', 'live', 'bnos' ]
+progs		= [ 'btt_plot_%s.py' % t for t in types ]
+
+get_base 	= lambda file: file[file.find('_')+1:file.rfind('_')]
+
+#------------------------------------------------------------------------------
+def fatal(msg):
+	"""Generate fatal error message and exit"""
+
+	print >>sys.stderr, 'FATAL: %s' % msg
+	sys.exit(1)
+
+#------------------------------------------------------------------------------
+def gen_legends(ax, legends):
+	leg = ax.legend(legends, 'best', shadow=True)
+	frame = leg.get_frame()
+	frame.set_facecolor('0.80')
+	for t in leg.get_texts():
+		t.set_fontsize('xx-small')
+
+#----------------------------------------------------------------------
+def get_data(files):
+	"""Retrieve data from files provided.
+
+	Returns a database containing:
+		'min_x', 'max_x' 	- Minimum and maximum X values found
+		'min_y', 'max_y' 	- Minimum and maximum Y values found
+		'x', 'y'		- X & Y value arrays
+		'ax', 'ay'		- Running average over X & Y --
+					  if > 10 values provided...
+	"""
+	#--------------------------------------------------------------
+	def check(mn, mx, v):
+		"""Returns new min, max, and float value for those passed in"""
+
+		v = float(v)
+		if mn == None or v < mn: mn = v
+		if mx == None or v > mx: mx = v
+		return mn, mx, v
+
+	#--------------------------------------------------------------
+	def avg(xs, ys):
+		"""Computes running average for Xs and Ys"""
+
+		#------------------------------------------------------
+		def _avg(vals):
+			"""Computes average for array of values passed"""
+
+			total = 0.0
+			for val in vals:
+				total += val
+			return total / len(vals)
+
+		#------------------------------------------------------
+		if len(xs) < 1000:
+			return xs, ys
+
+		axs = [xs[0]]
+		ays = [ys[0]]
+		_xs = [xs[0]]
+		_ys = [ys[0]]
+
+		x_range = (xs[-1] - xs[0]) / 100
+		for idx in range(1, len(ys)):
+			if (xs[idx] - _xs[0]) > x_range:
+				axs.append(_avg(_xs))
+				ays.append(_avg(_ys))
+				del _xs, _ys
+
+				_xs = [xs[idx]]
+				_ys = [ys[idx]]
+			else:
+				_xs.append(xs[idx])
+				_ys.append(ys[idx])
+
+		if len(_xs) > 1:
+			axs.append(_avg(_xs))
+			ays.append(_avg(_ys))
+
+		return axs, ays
+
+	#--------------------------------------------------------------
+	global verbose
+
+	db = {}
+	min_x = max_x = min_y = max_y = None
+	for file in files:
+		if not os.path.exists(file):
+			fatal('%s not found' % file)
+		elif verbose:
+			print 'Processing %s' % file
+
+		xs = []
+		ys = []
+		for line in open(file, 'r'):
+			f = line.rstrip().split(None)
+			if line.find('#') == 0 or len(f) < 2:
+				continue
+			(min_x, max_x, x) = check(min_x, max_x, f[0])
+			(min_y, max_y, y) = check(min_y, max_y, f[1])
+			xs.append(x)
+			ys.append(y)
+
+		db[file] = {'x':xs, 'y':ys}
+		if len(xs) > 10:
+			db[file]['ax'], db[file]['ay'] = avg(xs, ys)
+		else:
+			db[file]['ax'] = db[file]['ay'] = None
+
+	db['min_x'] = min_x
+	db['max_x'] = max_x
+	db['min_y'] = min_y
+	db['max_y'] = max_y
+	return db
+
+#----------------------------------------------------------------------
+def parse_args(args):
+	"""Parse command line arguments.
+
+	Returns list of (data) files that need to be processed -- /unless/
+	the -A (--generate-all) option is passed, in which case superfluous
+	data files are ignored...
+	"""
+
+	global add_legend, output_file, title_str, type, verbose
+	global generate_all
+
+	prog = args[0][args[0].rfind('/')+1:]
+	if prog == 'btt_plot.py':
+		pass
+	elif not prog in progs:
+		fatal('%s not a valid command name' % prog)
+	else:
+		type = prog[prog.rfind('_')+1:prog.rfind('.py')]
+
+	s_opts = 'ALo:t:T:v'
+	l_opts = [ 'generate-all', 'type', 'no-legend', 'output', 'title',
+		   'verbose' ]
+
+	try:
+		(opts, args) = getopt.getopt(args[1:], s_opts, l_opts)
+	except getopt.error, msg:
+		print >>sys.stderr, msg
+		fatal(__doc__)
+
+	for (o, a) in opts:
+		if o in ('-A', '--generate-all'):
+			generate_all = True
+		elif o in ('-L', '--no-legend'):
+			add_legend = False
+		elif o in ('-o', '--output'):
+			output_file = a
+		elif o in ('-t', '--type'):
+			if not a in types:
+				fatal('Type %s not supported' % a)
+			type = a
+		elif o in ('-T', '--title'):
+			title_str = a
+		elif o in ('-v', '--verbose'):
+			verbose = True
+
+	if type == None and not generate_all:
+		fatal('Need type of data files to process - (-t <type>)')
+
+	return args
+
+#------------------------------------------------------------------------------
+def gen_title(fig, type, title_str):
+	"""Sets the title for the figure based upon the type /or/ user title"""
+
+	if title_str != None:
+		pass
+	elif type == 'aqd':
+		title_str = 'Average Queue Depth'
+	elif type == 'bnos':
+		title_str = 'Block Numbers Accessed'
+	elif type == 'q2d':
+		title_str = 'Queue (Q) To Issue (D) Average Latencies'
+	elif type == 'd2c':
+		title_str = 'Issue (D) To Complete (C) Average Latencies'
+	elif type == 'q2c':
+		title_str = 'Queue (Q) To Complete (C) Average Latencies'
+
+	title = fig.text(.5, .95, title_str, horizontalalignment='center')
+	title.set_fontsize('large')
+
+#------------------------------------------------------------------------------
+def gen_labels(db, ax, type):
+	"""Generate X & Y 'axis'"""
+
+	#----------------------------------------------------------------------
+	def gen_ylabel(ax, type):
+		"""Set the Y axis label based upon the type"""
+
+		if type == 'aqd':
+			str = 'Number of Requests Queued'
+		elif type == 'bnos':
+			str = 'Block Number'
+		else:
+			str = 'Seconds'
+		ax.set_ylabel(str)
+
+	#----------------------------------------------------------------------
+	xdelta = 0.1 * (db['max_x'] - db['min_x'])
+	ydelta = 0.1 * (db['max_y'] - db['min_y'])
+
+	ax.set_xlim(db['min_x'] - xdelta, db['max_x'] + xdelta)
+	ax.set_ylim(db['min_y'] - ydelta, db['max_y'] + ydelta)
+	ax.set_xlabel('Runtime (seconds)')
+	ax.grid(True)
+	gen_ylabel(ax, type)
+
+#------------------------------------------------------------------------------
+def generate_output(type, db):
+	"""Generate the output plot based upon the type and database"""
+
+	#----------------------------------------------------------------------
+	def color(idx, style):
+		"""Returns a color/symbol type based upon the index passed."""
+
+                colors = [ 'b', 'g', 'r', 'c', 'm', 'y', 'k' ]
+		l_styles = [ '-', ':', '--', '-.' ]
+		m_styles = [ 'o', '+', '.', ',', 's', 'v', 'x', '<', '>' ]
+
+		color = colors[idx % len(colors)]
+		if style == 'line':
+			style = l_styles[(idx / len(l_styles)) % len(l_styles)]
+		elif style == 'marker':
+			style = m_styles[(idx / len(m_styles)) % len(m_styles)]
+
+		return '%s%s' % (color, style)
+
+	#----------------------------------------------------------------------
+	global add_legend, output_file, title_str, verbose
+
+	if output_file != None:
+		ofile = output_file
+	else:
+		ofile = '%s.png' % type
+
+	if verbose:
+		print 'Generating plot into %s' % ofile
+
+	fig = plt.figure(figsize=plot_size)
+	ax = fig.add_subplot(111)
+
+	gen_title(fig, type, title_str)
+	gen_labels(db, ax, type)
+
+	idx = 0
+	if add_legend:
+		legends = []
+	else:
+		legends = None
+
+	keys = []
+	for file in db.iterkeys():
+		if not file in ['min_x', 'max_x', 'min_y', 'max_y']:
+			keys.append(file)
+
+	keys.sort()
+	for file in keys:
+		dat = db[file]
+		if type == 'bnos':
+			ax.plot(dat['x'], dat['y'], color(idx, 'marker'),
+				markersize=1)
+		elif dat['ax'] == None:
+			continue	# Don't add legend
+		else:
+			ax.plot(dat['ax'], dat['ay'], color(idx, 'line'),
+				linewidth=1.0)
+		if add_legend:
+			legends.append(get_base(file))
+		idx += 1
+
+	if add_legend and len(legends) > 0:
+		gen_legends(ax, legends)
+	plt.savefig(ofile)
+
+#------------------------------------------------------------------------------
+def get_files(type):
+	"""Returns the list of files for the -A option based upon type"""
+
+	if type == 'bnos':
+		files = []
+		for fn in glob.glob('*c.dat'):
+			for t in [ 'q2q', 'd2d', 'q2c', 'd2c' ]:
+				if fn.find(t) >= 0:
+					break
+			else:
+				files.append(fn)
+	else:
+		files = glob.glob('*%s.dat' % type)
+	return files
+
+#------------------------------------------------------------------------------
+def do_bnos(files):
+	for file in files:
+		base = get_base(file)
+		title_str = 'Block Numbers Accessed: %s' % base
+		output_file = 'bnos_%s.png' % base
+		generate_output(t, get_data([file]))
+
+#------------------------------------------------------------------------------
+def do_live(files):
+	global plot_size
+
+	#----------------------------------------------------------------------
+	def get_live_data(fn):
+		xs = []
+		ys = []
+		for line in open(fn, 'r'):
+			f = line.rstrip().split()
+			if f[0] != '#' and len(f) == 2:
+				xs.append(float(f[0]))
+				ys.append(float(f[1]))
+		return xs, ys
+
+	#----------------------------------------------------------------------
+	def live_sort(a, b):
+		if a[0] == 'sys' and b[0] == 'sys':
+			return 0
+		elif a[0] == 'sys' or a[2][0] < b[2][0]:
+			return -1
+		elif b[0] == 'sys' or a[2][0] > b[2][0]:
+			return  1
+		else:
+			return  0
+
+	#----------------------------------------------------------------------
+	def turn_off_ticks(ax):
+		for tick in ax.xaxis.get_major_ticks():
+			tick.tick1On = tick.tick2On = False
+		for tick in ax.yaxis.get_major_ticks():
+			tick.tick1On = tick.tick2On = False
+		for tick in ax.xaxis.get_minor_ticks():
+			tick.tick1On = tick.tick2On = False
+		for tick in ax.yaxis.get_minor_ticks():
+			tick.tick1On = tick.tick2On = False
+
+	#----------------------------------------------------------------------
+	fig = plt.figure(figsize=plot_size)
+	ax = fig.add_subplot(111)
+
+	db = []
+	for fn in files:
+		if not os.path.exists(fn):
+			continue
+		(xs, ys) = get_live_data(fn)
+		db.append([fn[:fn.find('_live.dat')], xs, ys])
+	db.sort(live_sort)
+
+	for rec in db:
+		ax.plot(rec[1], rec[2])
+
+	gen_title(fig, 'live', 'Active I/O Per Device')
+	ax.set_xlabel('Runtime (seconds)')
+	ax.set_ylabel('Device')
+	ax.grid(False)
+
+	ax.set_xlim(-0.1, db[0][1][-1]+1)
+	ax.set_yticks([idx for idx in range(0, len(db))])
+	ax.yaxis.set_ticklabels([rec[0] for rec in db])
+	turn_off_ticks(ax)
+
+	plt.savefig('live.png')
+	plt.savefig('live.eps')
+
+#------------------------------------------------------------------------------
+if __name__ == '__main__':
+	files = parse_args(sys.argv)
+
+	if generate_all:
+		output_file = title_str = type = None
+		for t in types:
+			files = get_files(t)
+			if len(files) == 0:
+				continue
+			elif t == 'bnos':
+				do_bnos(files)
+			elif t == 'live':
+				do_live(files)
+			else:
+				generate_output(t, get_data(files))
+				continue
+
+	elif len(files) < 1:
+		fatal('Need data files to process')
+	else:
+		generate_output(type, get_data(files))
+	sys.exit(0)
diff --git a/btt/devmap.c b/btt/devmap.c
new file mode 100644
index 0000000..0553a9e
--- /dev/null
+++ b/btt/devmap.c
@@ -0,0 +1,93 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ *
+ */
+#include <stdio.h>
+#include "globals.h"
+
+struct devmap {
+	struct list_head head;
+	char device[32], devno[32];
+};
+
+LIST_HEAD(all_devmaps);
+
+static int dev_map_add(char *line)
+{
+	struct devmap *dmp;
+
+	if (strstr(line, "Device") != NULL)
+		return 1;
+
+	dmp = malloc(sizeof(struct devmap));
+	if (sscanf(line, "%s %s", dmp->device, dmp->devno) != 2) {
+		free(dmp);
+		return 1;
+	}
+
+	list_add_tail(&dmp->head, &all_devmaps);
+	return 0;
+}
+
+char *dev_map_find(__u32 device)
+{
+	char this[128];
+	struct list_head *p;
+
+	sprintf(this, "%u,%u", MAJOR(device), MINOR(device));
+	__list_for_each(p, &all_devmaps) {
+		struct devmap *dmp = list_entry(p, struct devmap, head);
+
+		if (!strcmp(this, dmp->devno))
+			return dmp->device;
+	}
+
+	return NULL;
+}
+
+int dev_map_read(char *fname)
+{
+	char line[256];
+	FILE *fp = my_fopen(fname, "r");
+
+	if (!fp) {
+		perror(fname);
+		return 1;
+	}
+
+	while (fscanf(fp, "%255[a-zA-Z0-9 :.,/_-]\n", line) == 1) {
+		if (dev_map_add(line))
+			break;
+	}
+
+	fclose(fp);
+	return 0;
+}
+
+void dev_map_exit(void)
+{
+	struct list_head *p, *q;
+
+	list_for_each_safe(p, q, &all_devmaps) {
+		struct devmap *dmp = list_entry(p, struct devmap, head);
+
+		list_del(&dmp->head);
+		free(dmp);
+	}
+}
diff --git a/btt/devs.c b/btt/devs.c
new file mode 100644
index 0000000..ccaae87
--- /dev/null
+++ b/btt/devs.c
@@ -0,0 +1,278 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ *
+ */
+#include <stdio.h>
+#include "globals.h"
+
+#define N_DEV_HASH	128
+#define DEV_HASH(dev)	((MAJOR(dev) ^ MINOR(dev)) & (N_DEV_HASH - 1))
+struct list_head	dev_heads[N_DEV_HASH];
+
+static inline void *dip_rb_mkhds(void)
+{
+	size_t len = N_IOP_TYPES * sizeof(struct rb_root);
+	return memset(malloc(len), 0, len);
+}
+
+static void __destroy(struct rb_node *n)
+{
+	if (n) {
+		struct io *iop = rb_entry(n, struct io, rb_node);
+
+		__destroy(n->rb_left);
+		__destroy(n->rb_right);
+		io_release(iop);
+	}
+}
+
+static void __destroy_heads(struct rb_root *roots)
+{
+	int i;
+
+	for (i = 0; i < N_IOP_TYPES; i++)
+		__destroy(roots[i].rb_node);
+
+	free(roots);
+}
+
+void init_dev_heads(void)
+{
+	int i;
+	for (i = 0; i < N_DEV_HASH; i++)
+		INIT_LIST_HEAD(&dev_heads[i]);
+}
+
+struct d_info *__dip_find(__u32 device)
+{
+	struct d_info *dip;
+	struct list_head *p;
+
+	__list_for_each(p, &dev_heads[DEV_HASH(device)]) {
+		dip = list_entry(p, struct d_info, hash_head);
+		if (device == dip->device)
+			return dip;
+	}
+
+	return NULL;
+}
+
+void __dip_exit(struct d_info *dip)
+{
+	list_del(&dip->all_head);
+	__destroy_heads(dip->heads);
+	region_exit(&dip->regions);
+	seeki_free(dip->seek_handle);
+	seeki_free(dip->q2q_handle);
+	aqd_free(dip->aqd_handle);
+	plat_free(dip->q2d_plat_handle);
+	plat_free(dip->q2c_plat_handle);
+	plat_free(dip->d2c_plat_handle);
+	p_live_free(dip->p_live_handle);
+	bno_dump_free(dip->bno_dump_handle);
+	unplug_hist_free(dip->up_hist_handle);
+	rstat_free(dip->rstat_handle);
+	if (output_all_data)
+		q2d_free(dip->q2d_priv);
+	if (dip->pit_fp)
+		fclose(dip->pit_fp);
+	free(dip);
+}
+
+void dip_exit(void)
+{
+	struct list_head *p, *q;
+
+	list_for_each_safe(p, q, &all_devs) {
+		struct d_info *dip = list_entry(p, struct d_info, all_head);
+		__dip_exit(dip);
+	}
+}
+
+static inline FILE *open_pit(struct d_info *dip)
+{
+	FILE *fp;
+	char str[256];
+
+	sprintf(str, "%s_pit.dat", dip->dip_name);
+	if ((fp = my_fopen(str, "w")) == NULL)
+		perror(str);
+
+	return fp;
+}
+
+struct d_info *dip_alloc(__u32 device, struct io *iop)
+{
+	struct d_info *dip = __dip_find(device);
+
+	if (dip == NULL) {
+		dip = malloc(sizeof(struct d_info));
+		memset(dip, 0, sizeof(*dip));
+		dip->device = device;
+		dip->devmap = dev_map_find(device);
+		dip->last_q = (__u64)-1;
+		dip->heads = dip_rb_mkhds();
+		region_init(&dip->regions);
+		dip->start_time = BIT_TIME(iop->t.time);
+		dip->pre_culling = 1;
+
+		mkhandle(dip, dip->dip_name, 256);
+
+		latency_alloc(dip);
+		dip->aqd_handle = aqd_alloc(dip);
+		dip->bno_dump_handle = bno_dump_alloc(dip);
+		dip->up_hist_handle = unplug_hist_alloc(dip);
+		dip->seek_handle = seeki_alloc(dip, "_d2d");
+		dip->q2q_handle = seeki_alloc(dip, "_q2q");
+		dip->q2d_plat_handle = plat_alloc(dip, "_q2d");
+		dip->q2c_plat_handle = plat_alloc(dip, "_q2c");
+		dip->d2c_plat_handle = plat_alloc(dip, "_d2c");
+		dip->rstat_handle = rstat_alloc(dip);
+		dip->p_live_handle = p_live_alloc();
+
+		if (per_io_trees)
+			dip->pit_fp = open_pit(dip);
+
+		if (output_all_data)
+			dip->q2d_priv = q2d_alloc();
+
+		list_add_tail(&dip->hash_head, &dev_heads[DEV_HASH(device)]);
+		list_add_tail(&dip->all_head, &all_devs);
+		n_devs++;
+	}
+
+	if (dip->pre_culling) {
+		if (iop->type == IOP_Q || iop->type == IOP_A)
+			dip->pre_culling = 0;
+		else
+			return NULL;
+	}
+
+	iop->linked = dip_rb_ins(dip, iop);
+	dip->end_time = BIT_TIME(iop->t.time);
+
+	return dip;
+}
+
+void iop_rem_dip(struct io *iop)
+{
+	if (iop->linked) {
+		dip_rb_rem(iop);
+		iop->linked = 0;
+	}
+}
+
+void dip_foreach(struct io *iop, enum iop_type type,
+		 void (*fnc)(struct io *iop, struct io *this), int rm_after)
+{
+	if (rm_after) {
+		LIST_HEAD(head);
+		struct io *this;
+		struct list_head *p, *q;
+
+		dip_rb_fe(iop->dip, type, iop, fnc, &head);
+		list_for_each_safe(p, q, &head) {
+			this = list_entry(p, struct io, f_head);
+			list_del(&this->f_head);
+			io_release(this);
+		}
+	} else
+		dip_rb_fe(iop->dip, type, iop, fnc, NULL);
+}
+
+void dip_foreach_list(struct io *iop, enum iop_type type, struct list_head *hd)
+{
+	dip_rb_fe(iop->dip, type, iop, NULL, hd);
+}
+
+struct io *dip_find_sec(struct d_info *dip, enum iop_type type, __u64 sec)
+{
+	return dip_rb_find_sec(dip, type, sec);
+}
+
+void dip_foreach_out(void (*func)(struct d_info *, void *), void *arg)
+{
+	if (devices == NULL) {
+		struct list_head *p;
+		__list_for_each(p, &all_devs)
+			func(list_entry(p, struct d_info, all_head), arg);
+	} else {
+		int i;
+		struct d_info *dip;
+		unsigned int mjr, mnr;
+		char *p = devices;
+
+		while (p && ((i = sscanf(p, "%u,%u", &mjr, &mnr)) == 2)) {
+			dip = __dip_find((__u32)((mjr << MINORBITS) | mnr));
+			func(dip, arg);
+			p = strchr(p, ';');
+			if (p) p++;
+		}
+	}
+}
+
+void dip_plug(__u32 dev, double cur_time)
+{
+	struct d_info *dip = __dip_find(dev);
+
+	if (dip && !dip->is_plugged) {
+		dip->is_plugged = 1;
+		dip->last_plug = cur_time;
+	}
+}
+
+static inline void unplug(struct d_info *dip, double cur_time)
+{
+	dip->is_plugged = 0;
+	dip->plugged_time += (cur_time - dip->last_plug);
+}
+
+void dip_unplug(__u32 dev, double cur_time, __u64 nios_up)
+{
+	struct d_info *dip = __dip_find(dev);
+
+	if (dip && dip->is_plugged) {
+		dip->nplugs++;
+		dip->nios_up += nios_up;
+		unplug(dip, cur_time);
+	}
+}
+
+void dip_unplug_tm(__u32 dev, double cur_time, __u64 nios_up)
+{
+	struct d_info *dip = __dip_find(dev);
+
+	if (dip && dip->is_plugged) {
+		dip->nios_upt += nios_up;
+		dip->nplugs_t++;
+		unplug(dip, cur_time);
+	}
+}
+
+void dip_cleanup(void)
+{
+	struct list_head *p, *q;
+
+	list_for_each_safe(p, q, &all_devs) {
+		struct d_info *dip = list_entry(p, struct d_info, all_head);
+
+		if (dip->n_qs == 0 && dip->n_ds == 0)
+			__dip_exit(dip);
+	}
+}
diff --git a/btt/dip_rb.c b/btt/dip_rb.c
new file mode 100644
index 0000000..867a97b
--- /dev/null
+++ b/btt/dip_rb.c
@@ -0,0 +1,86 @@
+/*
+ * blktrace output analysis: generate a timeline & gather statistics
+ *
+ * Copyright (C) 2006 Alan D. Brunelle <Alan.Brunelle@hp.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; 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
+ *
+ */
+#include <stdio.h>
+#include "globals.h"
+
+int rb_insert(struct rb_root *root, struct io *iop)
+{
+	struct io *__iop;
+	struct rb_node *parent = NULL;
+	struct rb_node **p = &root->rb_node;
+	__u64 __s, s = BIT_START(iop);
+
+	while (*p) {
+		parent = *p;
+		__iop = rb_entry(parent, struct io, rb_node);
+		__s = BIT_START(__iop);
+
+		if (s < __s)
+			p = &(*p)->rb_left;
+		else if (s > __s)
+			p = &(*p)->rb_right;
+		else
+			return 0;
+	}
+
+	rb_link_node(&iop->rb_node, parent, p);
+	rb_insert_color(&iop->rb_node, root);
+	return 1;
+}
+
+struct io *rb_find_sec(struct rb_root *root, __u64 sec)
+{
+	struct io *__iop;
+	struct rb_node *n = root->rb_node;
+
+	while (n) {
+		__iop = rb_entry(n, struct io, rb_node);
+		if (sec < BIT_START(__iop))
+			n = n->rb_left;
+		else if (sec >= BIT_END(__iop))
+			n = n->rb_right;
+		else
+			return __iop;
+	}
+
+	return NULL;
+}
+
+void rb_foreach(struct rb_node *n, struct io *iop,
+		      void (*fnc)(struct io *iop, struct io *this),
+		      struct list_head *head)
+{
+	if (n) {
+		struct io *this = rb_entry(n, struct io, rb_node);
+		__u64 iop_s = BIT_START(iop), iop_e = BIT_END(iop);
+		__u64 this_s = BIT_START(this), this_e = BIT_END(this);
+
+		if ((iop_s <= this_s) && (this_e <= iop_e)) {
+			if (fnc) fnc(iop, this);
+			if (head)
+				list_add_tail(&this->f_head, head);
+		}
+		if (iop_s < this_s)
+			rb_foreach(n->rb_left, iop, fnc, head);
+		if (this_e < iop_e)
+			rb_foreach(n->rb_right, iop, fnc, head);
+	}
+}
diff --git a/btt/doc/Makefile b/btt/doc/Makefile
new file mode 100644
index 0000000..2e6fd88
--- /dev/null
+++ b/btt/doc/Makefile
@@ -0,0 +1,15 @@
+DOCTMP	= btt.log btt.aux btt.dvi btt.toc btt.tex.bak
+
+all: btt.pdf
+
+btt.tex: activity.eps qhist.eps dhist.eps seek.eps rstats.eps live.eps
+	@touch btt.tex
+
+btt.pdf: btt.tex
+	@latex btt.tex
+	@latex btt.tex
+	@dvipdfm -p a4 btt
+	@rm -rf $(DOCTMP)
+
+clean:
+	-rm -f btt.pdf $(DOCTMP)
diff --git a/btt/doc/activity.eps b/btt/doc/activity.eps
new file mode 100644
index 0000000..034533f
--- /dev/null
+++ b/btt/doc/activity.eps
@@ -0,0 +1,923 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%BoundingBox: 0 0 799 635
+%%LanguageLevel: 2
+%%Creator: Grace-5.1.20
+%%CreationDate: Thu Mar  1 14:50:45 2007
+%%DocumentData: Clean8Bit
+%%Orientation: Portrait
+%%Title: Untitled
+%%For: adb
+%%DocumentNeededResources: (atend)
+%%EndComments
+%%BeginProlog
+/m {moveto} def
+/l {lineto} def
+/s {stroke} def
+/n {newpath} def
+/c {closepath} def
+/RL {rlineto} def
+/SLW {setlinewidth} def
+/GS {gsave} def
+/GR {grestore} def
+/SC {setcolor} def
+/SGRY {setgray} def
+/SRGB {setrgbcolor} def
+/SD {setdash} def
+/SLC {setlinecap} def
+/SLJ {setlinejoin} def
+/SCS {setcolorspace} def
+/FFSF {findfont setfont} def
+/CC {concat} def
+/PXL {n m 0 0 RL s} def
+/Color0 {1.0000 1.0000 1.0000} def
+/Color1 {0.0000 0.0000 0.0000} def
+/Color2 {1.0000 0.0000 0.0000} def
+/Color3 {0.0000 1.0000 0.0000} def
+/Color4 {0.0000 0.0000 1.0000} def
+/Color5 {1.0000 1.0000 0.0000} def
+/Color6 {0.7373 0.5608 0.5608} def
+/Color7 {0.8627 0.8627 0.8627} def
+/Color8 {0.5804 0.0000 0.8275} def
+/Color9 {0.0000 1.0000 1.0000} def
+/Color10 {1.0000 0.0000 1.0000} def
+/Color11 {1.0000 0.6471 0.0000} def
+/Color12 {0.4471 0.1294 0.7373} def
+/Color13 {0.4039 0.0275 0.2824} def
+/Color14 {0.2510 0.8784 0.8157} def
+/Color15 {0.0000 0.5451 0.0000} def
+/PTRN {
+ /pat_bits exch def 
+ <<
+  /PaintType 2
+  /PatternType 1 /TilingType 1
+  /BBox[0 0 16 16]
+  /XStep 16 /YStep 16
+  /PaintProc {
+   pop
+   16 16 true [-1 0 0 -1 16 16] pat_bits imagemask
+  }
+ >>
+ [0.0016 0 0 0.0016 0 0]
+ makepattern
+} def
+/Pattern0 {<0000000000000000000000000000000000000000000000000000000000000000> PTRN} bind def
+/Pattern1 {<ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff> PTRN} bind def
+/Pattern2 {<eeeeffffbbbbffffeeeeffffbbbbffffeeeeffffbbbbffffeeeeffffbbbbffff> PTRN} bind def
+/Pattern3 {<eeeebbbbeeeebbbbeeeebbbbeeeebbbbeeeebbbbeeeebbbbeeeebbbbeeeebbbb> PTRN} bind def
+/Pattern4 {<5555aaaa5555aaaa5555aaaa5555aaaa5555aaaa5555aaaa5555aaaa5555aaaa> PTRN} bind def
+/Pattern5 {<1111444411114444111144441111444411114444111144441111444411114444> PTRN} bind def
+/Pattern6 {<1111000044440000111100004444000011110000444400001111000044440000> PTRN} bind def
+/Pattern7 {<1010000000000000010100000000000010100000000000000101000000000000> PTRN} bind def
+/Pattern8 {<0000000000000000000000000000000000000000000000000000000000000000> PTRN} bind def
+/Pattern9 {<1e1e0f0f8787c3c3e1e1f0f078783c3c1e1e0f0f8787c3c3e1e1f0f078783c3c> PTRN} bind def
+/Pattern10 {<7878f0f0e1e1c3c387870f0f1e1e3c3c7878f0f0e1e1c3c387870f0f1e1e3c3c> PTRN} bind def
+/Pattern11 {<3333333333333333333333333333333333333333333333333333333333333333> PTRN} bind def
+/Pattern12 {<ffffffff00000000ffffffff00000000ffffffff00000000ffffffff00000000> PTRN} bind def
+/Pattern13 {<8181424224241818181824244242818181814242242418181818242442428181> PTRN} bind def
+/Pattern14 {<8080404020201010080804040202010180804040202010100808040402020101> PTRN} bind def
+/Pattern15 {<0101020204040808101020204040808001010202040408081010202040408080> PTRN} bind def
+/Pattern16 {<2222222222222222222222222222222222222222222222222222222222222222> PTRN} bind def
+/Pattern17 {<0000ffff000000000000ffff000000000000ffff000000000000ffff00000000> PTRN} bind def
+/Pattern18 {<2222ffff222222222222ffff222222222222ffff222222222222ffff22222222> PTRN} bind def
+/Pattern19 {<ffffffff33333333ffffffff33333333ffffffff33333333ffffffff33333333> PTRN} bind def
+/Pattern20 {<0f0f0f0f0f0f0f0ff0f0f0f0f0f0f0f00f0f0f0f0f0f0f0ff0f0f0f0f0f0f0f0> PTRN} bind def
+/Pattern21 {<ff00ff00ff00ff00ff00ff00ff00ff0000ff00ff00ff00ff00ff00ff00ff00ff> PTRN} bind def
+/Pattern22 {<8001800180018001800180018001ffffffff8001800180018001800180018001> PTRN} bind def
+/Pattern23 {<c003c003c003c003c003c003ffffffffffffffffc003c003c003c003c003c003> PTRN} bind def
+/Pattern24 {<040404040404ffff404040404040ffff040404040404ffff404040404040ffff> PTRN} bind def
+/Pattern25 {<180018001800180018001800ffffffff001800180018001800180018ffffffff> PTRN} bind def
+/Pattern26 {<1111b8b87c7c3a3a1111a3a3c7c78b8b1111b8b87c7c3a3a1111a3a3c7c78b8b> PTRN} bind def
+/Pattern27 {<101010102828c7c70101010182827c7c101010102828c7c70101010182827c7c> PTRN} bind def
+/Pattern28 {<1c1c121211112121c1c12121111112121c1c121211112121c1c1212111111212> PTRN} bind def
+/Pattern29 {<3e3e414180808080e3e31414080808083e3e414180808080e3e3141408080808> PTRN} bind def
+/Pattern30 {<4848888884848383848488884848383848488888848483838484888848483838> PTRN} bind def
+/Pattern31 {<03030404080808080c0c12122121c0c003030404080808080c0c12122121c0c0> PTRN} bind def
+/ellipsedict 8 dict def
+ellipsedict /mtrx matrix put
+/EARC {
+ ellipsedict begin
+  /endangle exch def
+  /startangle exch def
+  /yrad exch def
+  /xrad exch def
+  /y exch def
+  /x exch def
+  /savematrix mtrx currentmatrix def
+  x y translate
+  xrad yrad scale
+  0 0 1 startangle endangle arc
+  savematrix setmatrix
+ end
+} def
+/TL {
+  /kcomp exch def
+  /linewidth exch def
+  /offset exch def
+  GS
+  0 offset rmoveto
+  linewidth SLW
+  dup stringwidth exch kcomp add exch RL s
+  GR
+} def
+/KINIT
+{
+ /kvector exch def
+ /kid 0 def
+} def
+/KPROC
+{
+ pop pop
+ kvector kid get
+ 0 rmoveto
+ /kid 1 kid add def
+} def
+/DefEncoding [
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /space
+ /exclam
+ /quotedbl
+ /numbersign
+ /dollar
+ /percent
+ /ampersand
+ /quoteright
+ /parenleft
+ /parenright
+ /asterisk
+ /plus
+ /comma
+ /hyphen
+ /period
+ /slash
+ /zero
+ /one
+ /two
+ /three
+ /four
+ /five
+ /six
+ /seven
+ /eight
+ /nine
+ /colon
+ /semicolon
+ /less
+ /equal
+ /greater
+ /question
+ /at
+ /A
+ /B
+ /C
+ /D
+ /E
+ /F
+ /G
+ /H
+ /I
+ /J
+ /K
+ /L
+ /M
+ /N
+ /O
+ /P
+ /Q
+ /R
+ /S
+ /T
+ /U
+ /V
+ /W
+ /X
+ /Y
+ /Z
+ /bracketleft
+ /backslash
+ /bracketright
+ /asciicircum
+ /underscore
+ /grave
+ /a
+ /b
+ /c
+ /d
+ /e
+ /f
+ /g
+ /h
+ /i
+ /j
+ /k
+ /l
+ /m
+ /n
+ /o
+ /p
+ /q
+ /r
+ /s
+ /t
+ /u
+ /v
+ /w
+ /x
+ /y
+ /z
+ /braceleft
+ /bar
+ /braceright
+ /asciitilde
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /space
+ /exclamdown
+ /cent
+ /sterling
+ /currency
+ /yen
+ /brokenbar
+ /section
+ /dieresis
+ /copyright
+ /ordfeminine
+ /guillemotleft
+ /logicalnot
+ /hyphen
+ /registered
+ /macron
+ /degree
+ /plusminus
+ /twosuperior
+ /threesuperior
+ /acute
+ /mu
+ /paragraph
+ /periodcentered
+ /cedilla
+ /onesuperior
+ /ordmasculine
+ /guillemotright
+ /onequarter
+ /onehalf
+ /threequarters
+ /questiondown
+ /Agrave
+ /Aacute
+ /Acircumflex
+ /Atilde
+ /Adieresis
+ /Aring
+ /AE
+ /Ccedilla
+ /Egrave
+ /Eacute
+ /Ecircumflex
+ /Edieresis
+ /Igrave
+ /Iacute
+ /Icircumflex
+ /Idieresis
+ /Eth
+ /Ntilde
+ /Ograve
+ /Oacute
+ /Ocircumflex
+ /Otilde
+ /Odieresis
+ /multiply
+ /Oslash
+ /Ugrave
+ /Uacute
+ /Ucircumflex
+ /Udieresis
+ /Yacute
+ /Thorn
+ /germandbls
+ /agrave
+ /aacute
+ /acircumflex
+ /atilde
+ /adieresis
+ /aring
+ /ae
+ /ccedilla
+ /egrave
+ /eacute
+ /ecircumflex
+ /edieresis
+ /igrave
+ /iacute
+ /icircumflex
+ /idieresis
+ /eth
+ /ntilde
+ /ograve
+ /oacute
+ /ocircumflex
+ /otilde
+ /odieresis
+ /divide
+ /oslash
+ /ugrave
+ /uacute
+ /ucircumflex
+ /udieresis
+ /yacute
+ /thorn
+ /ydieresis
+] def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+612.00 612.00 scale
+n
+0.0000 0.0000 m
+0.0000 1.0000 l
+1.2941 1.0000 l
+1.2941 0.0000 l
+c
+[/DeviceRGB] SCS
+Color0 SC
+fill
+[/DeviceRGB] SCS
+Color6 SC
+[0.0015 0.0045 ] 0 SD
+0.0015 SLW
+0 SLC
+0 SLJ
+n
+0.1667 0.1500 m
+0.1667 0.8500 l
+s
+n
+0.1833 0.1500 m
+0.1833 0.8500 l
+s
+n
+0.2000 0.1500 m
+0.2000 0.8500 l
+s
+n
+0.2167 0.1500 m
+0.2167 0.8500 l
+s
+n
+0.2333 0.1500 m
+0.2333 0.8500 l
+s
+n
+0.2667 0.1500 m
+0.2667 0.8500 l
+s
+n
+0.2833 0.1500 m
+0.2833 0.8500 l
+s
+n
+0.3000 0.1500 m
+0.3000 0.8500 l
+s
+n
+0.3167 0.1500 m
+0.3167 0.8500 l
+s
+n
+0.3333 0.1500 m
+0.3333 0.8500 l
+s
+n
+0.3667 0.1500 m
+0.3667 0.8500 l
+s
+n
+0.3833 0.1500 m
+0.3833 0.8500 l
+s
+n
+0.4000 0.1500 m
+0.4000 0.8500 l
+s
+n
+0.4167 0.1500 m
+0.4167 0.8500 l
+s
+n
+0.4333 0.1500 m
+0.4333 0.8500 l
+s
+n
+0.4667 0.1500 m
+0.4667 0.8500 l
+s
+n
+0.4833 0.1500 m
+0.4833 0.8500 l
+s
+n
+0.5000 0.1500 m
+0.5000 0.8500 l
+s
+n
+0.5167 0.1500 m
+0.5167 0.8500 l
+s
+n
+0.5333 0.1500 m
+0.5333 0.8500 l
+s
+n
+0.5667 0.1500 m
+0.5667 0.8500 l
+s
+n
+0.5833 0.1500 m
+0.5833 0.8500 l
+s
+n
+0.6000 0.1500 m
+0.6000 0.8500 l
+s
+n
+0.6167 0.1500 m
+0.6167 0.8500 l
+s
+n
+0.6333 0.1500 m
+0.6333 0.8500 l
+s
+n
+0.6667 0.1500 m
+0.6667 0.8500 l
+s
+n
+0.6833 0.1500 m
+0.6833 0.8500 l
+s
+n
+0.7000 0.1500 m
+0.7000 0.8500 l
+s
+n
+0.7167 0.1500 m
+0.7167 0.8500 l
+s
+n
+0.7333 0.1500 m
+0.7333 0.8500 l
+s
+n
+0.7667 0.1500 m
+0.7667 0.8500 l
+s
+n
+0.7833 0.1500 m
+0.7833 0.8500 l
+s
+n
+0.8000 0.1500 m
+0.8000 0.8500 l
+s
+n
+0.8167 0.1500 m
+0.8167 0.8500 l
+s
+n
+0.8333 0.1500 m
+0.8333 0.8500 l
+s
+n
+0.8667 0.1500 m
+0.8667 0.8500 l
+s
+n
+0.8833 0.1500 m
+0.8833 0.8500 l
+s
+n
+0.9000 0.1500 m
+0.9000 0.8500 l
+s
+n
+0.9167 0.1500 m
+0.9167 0.8500 l
+s
+n
+0.9333 0.1500 m
+0.9333 0.8500 l
+s
+n
+0.9667 0.1500 m
+0.9667 0.8500 l
+s
+n
+0.9833 0.1500 m
+0.9833 0.8500 l
+s
+n
+1.0000 0.1500 m
+1.0000 0.8500 l
+s
+n
+1.0167 0.1500 m
+1.0167 0.8500 l
+s
+n
+1.0333 0.1500 m
+1.0333 0.8500 l
+s
+n
+1.0667 0.1500 m
+1.0667 0.8500 l
+s
+n
+1.0833 0.1500 m
+1.0833 0.8500 l
+s
+n
+1.1000 0.1500 m
+1.1000 0.8500 l
+s
+n
+1.1167 0.1500 m
+1.1167 0.8500 l
+s
+n
+1.1333 0.1500 m
+1.1333 0.8500 l
+s
+[0.0075 0.0045 ] 0 SD
+0.0015 SLW
+n
+0.1500 0.1500 m
+0.1500 0.8500 l
+s
+n
+0.2500 0.1500 m
+0.2500 0.8500 l
+s
+n
+0.3500 0.1500 m
+0.3500 0.8500 l
+s
+n
+0.4500 0.1500 m
+0.4500 0.8500 l
+s
+n
+0.5500 0.1500 m
+0.5500 0.8500 l
+s
+n
+0.6500 0.1500 m
+0.6500 0.8500 l
+s
+n
+0.7500 0.1500 m
+0.7500 0.8500 l
+s
+n
+0.8500 0.1500 m
+0.8500 0.8500 l
+s
+n
+0.9500 0.1500 m
+0.9500 0.8500 l
+s
+n
+1.0500 0.1500 m
+1.0500 0.8500 l
+s
+n
+1.1500 0.1500 m
+1.1500 0.8500 l
+s
+[0.0015 0.0045 ] 0 SD
+0.0015 SLW
+n
+0.1500 0.1850 m
+1.1500 0.1850 l
+s
+n
+0.1500 0.5350 m
+1.1500 0.5350 l
+s
+[/DeviceRGB] SCS
+Color1 SC
+[] 0 SD
+0.0015 SLW
+n
+0.1500 0.1850 m
+0.1500 0.4650 l
+0.1624 0.4650 l
+0.1624 0.1850 l
+0.1832 0.1850 l
+0.1832 0.4650 l
+0.1832 0.4650 l
+0.1832 0.1850 l
+0.2105 0.1850 l
+0.2105 0.4650 l
+0.2323 0.4650 l
+0.2323 0.1850 l
+0.2801 0.1850 l
+0.2801 0.4650 l
+0.2808 0.4650 l
+0.2808 0.1850 l
+0.2917 0.1850 l
+0.2917 0.4650 l
+0.2917 0.4650 l
+0.2917 0.1850 l
+0.3313 0.1850 l
+0.3313 0.4650 l
+0.3456 0.4650 l
+0.3456 0.1850 l
+0.3912 0.1850 l
+0.3912 0.4650 l
+0.3912 0.4650 l
+0.3912 0.1850 l
+0.4073 0.1850 l
+0.4073 0.4650 l
+0.4083 0.4650 l
+0.4083 0.1850 l
+0.4248 0.1850 l
+0.4248 0.4650 l
+0.4329 0.4650 l
+0.4329 0.1850 l
+0.6819 0.1850 l
+0.6819 0.4650 l
+0.6873 0.4650 l
+0.6873 0.1850 l
+0.7791 0.1850 l
+0.7791 0.4650 l
+1.0873 0.4650 l
+1.0873 0.1850 l
+1.1122 0.1850 l
+1.1122 0.4650 l
+1.1139 0.4650 l
+1.1139 0.1850 l
+1.1496 0.1850 l
+1.1496 0.4650 l
+1.1496 0.4650 l
+1.1496 0.1850 l
+1.1501 0.1850 l
+s
+[/DeviceRGB] SCS
+Color2 SC
+n
+0.1507 0.5350 m
+0.1507 0.8150 l
+0.1629 0.8150 l
+0.1629 0.5350 l
+0.1836 0.5350 l
+0.1836 0.8150 l
+0.1836 0.8150 l
+0.1836 0.5350 l
+0.2105 0.5350 l
+0.2105 0.8150 l
+0.2323 0.8150 l
+0.2323 0.5350 l
+0.2803 0.5350 l
+0.2803 0.8150 l
+0.2811 0.8150 l
+0.2811 0.5350 l
+0.2921 0.5350 l
+0.2921 0.8150 l
+0.2921 0.8150 l
+0.2921 0.5350 l
+0.3316 0.5350 l
+0.3316 0.8150 l
+0.3457 0.8150 l
+0.3457 0.5350 l
+0.3912 0.5350 l
+0.3912 0.8150 l
+0.3912 0.8150 l
+0.3912 0.5350 l
+0.4075 0.5350 l
+0.4075 0.8150 l
+0.4086 0.8150 l
+0.4086 0.5350 l
+0.4250 0.5350 l
+0.4250 0.8150 l
+0.4333 0.8150 l
+0.4333 0.5350 l
+0.6828 0.5350 l
+0.6828 0.8150 l
+0.6877 0.8150 l
+0.6877 0.5350 l
+0.7799 0.5350 l
+0.7799 0.8150 l
+1.0879 0.8150 l
+1.0879 0.5350 l
+1.1127 0.5350 l
+1.1127 0.8150 l
+1.1143 0.8150 l
+1.1143 0.5350 l
+1.1499 0.5350 l
+1.1499 0.8150 l
+1.1499 0.8150 l
+1.1499 0.5350 l
+1.1501 0.5350 l
+s
+/Times-Roman findfont
+dup length dict begin
+ {1 index /FID ne {def} {pop pop} ifelse} forall
+ /Encoding DefEncoding def
+ currentdict
+end
+/Font32 exch definefont pop
+/Font32 FFSF
+[/DeviceRGB] SCS
+Color6 SC
+0.1447 0.1247 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(0) show
+GR
+/Font32 FFSF
+0.2455 0.1247 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(1) show
+GR
+/Font32 FFSF
+0.3447 0.1247 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(2) show
+GR
+/Font32 FFSF
+0.4451 0.1247 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(3) show
+GR
+/Font32 FFSF
+0.5447 0.1247 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(4) show
+GR
+/Font32 FFSF
+0.6451 0.1247 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(5) show
+GR
+/Font32 FFSF
+0.7447 0.1247 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(6) show
+GR
+/Font32 FFSF
+0.8449 0.1251 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(7) show
+GR
+/Font32 FFSF
+0.9451 0.1247 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(8) show
+GR
+/Font32 FFSF
+1.0449 0.1247 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(9) show
+GR
+/Font32 FFSF
+1.1390 0.1247 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(10) show
+GR
+/Font32 FFSF
+0.5453 0.0951 m
+GS
+[0.0280 0.0000 0.0000 0.0280 0 0] CC
+(Runtime \(seconds\)) show
+GR
+n
+0.1500 0.1500 m
+0.1500 0.8500 l
+1.1500 0.8500 l
+1.1500 0.1500 l
+0.1500 0.1500 l
+c
+s
+n
+0.8500 0.8000 m
+0.8500 0.7292 l
+1.0190 0.7292 l
+1.0190 0.8000 l
+c
+[/DeviceRGB] SCS
+Color0 SC
+fill
+[/DeviceRGB] SCS
+Color1 SC
+n
+0.8500 0.8000 m
+0.8500 0.7292 l
+1.0190 0.7292 l
+1.0190 0.8000 l
+0.8500 0.8000 l
+c
+s
+/Font32 FFSF
+0.9208 0.7743 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(Q activity) show
+GR
+n
+0.8608 0.7798 m
+0.9008 0.7798 l
+s
+/Font32 FFSF
+0.9208 0.7439 m
+GS
+[0.0224 0.0000 0.0000 0.0224 0 0] CC
+(C activity) show
+GR
+[/DeviceRGB] SCS
+Color2 SC
+n
+0.8608 0.7494 m
+0.9008 0.7494 l
+s
+/Font32 FFSF
+[/DeviceRGB] SCS
+Color1 SC
+0.4073 0.9190 m
+GS
+[0.0420 0.0000 0.0000 0.0420 0 0] CC
+(BTT Sample Q & C Activity) show
+GR
+%%Trailer
+%%DocumentNeededResources: font Times-Roman
+%%EOF
diff --git a/btt/doc/bno_plot.eps b/btt/doc/bno_plot.eps
new file mode 100644
index 0000000..b18dd40
--- /dev/null
+++ b/btt/doc/bno_plot.eps
@@ -0,0 +1,7696 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: GIMP PostScript file plugin V 1.17 by Peter Kirchgessner
+%%Title: bno_plot.eps
+%%CreationDate: Tue Feb 12 08:40:40 2008
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%Pages: 1
+%%BoundingBox: 14 14 1213 1564
+%%EndComments
+%%BeginProlog
+% Use own dictionary to avoid conflicts
+10 dict begin
+%%EndProlog
+%%Page: 1 1
+% Translate for offset
+14.173228346456694 14.173228346456694 translate
+% Translate to begin of first scanline
+0 1549 translate
+1198 -1549 scale
+% Image geometry
+1198 1549 8
+% Transformation matrix
+[ 1198 0 0 1549 0 0 ]
+% Strings to hold RGB-samples per scanline
+/rstr 1198 string def
+/gstr 1198 string def
+/bstr 1198 string def
+{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop}
+{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop}
+{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop}
+true 3
+%%BeginData:       427315 ASCII Bytes
+colorimage
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*Ws6]jel0eNNdI$u7qX4:^rpKddo'QJXo^2\ZJaS*WJaS*WddI,8J,~>
+Ja.gOJa.gOs69Ral0A6JdHU]3qWe"Zrp'L`o'-2To]cDVJa.gOJa.gOdd$i4J,~>
+J`_OGJ`_OGs5j:]l/qsFdH1E/qW@_VroX4\o&]oPo]?,RJ`_OGJ`_OGdcUQ0J,~>
+JaS*WJaS*Ws6]jeqsFFar9aObp[/"]rpBadiU-[GoBlSYq<e=bmd:)SmJm7XmJm7[mK*CV!.jQh
+mXaeWmaLV(!.Y~>
+Ja.gOJa.gOs69Raqs".]r9=7^pZ__YrosI`iT^CCoBH;Uq<A%^lKSBKl2UhTl2UhWl2gtN!.jEd
+l@J5OlI5%u!.Y~>
+J`_OGJ`_OGs5j:]qrRkYr8mtZpZ;GUroO1\iT:+?oB$#Qq;qbZk2l[Cjo>DPjo>DSjoPPF!.j9`
+k(2ZGk0rJm!.Y~>
+JaS*WJaS*Ws6]jeqsFFar9aObp[/"]h<k7CoBlSYq<e=bmd:)SmJm7XmJm7\mJm7dmJm6<mXaeW
+mXafUmf*9;~>
+Ja.gOJa.gOs69Raqs".]r9=7^pZ__Yh<Ft?oBH;Uq<A%^lKSBKl2UhTl2UhXl2Uh`l2Ug8l@J5O
+l@J6MlMgj7~>
+J`_OGJ`_OGs5j:]qrRkYr8mtZpZ;GUh<"\;oB$#Qq;qbZk2l[Cjo>DPjo>DTjo>D\jo>C4k(2ZG
+k(2[Ek5PF3~>
+JaS*WJaS*Ws6fgcs6fgcs6fgcq<e4_qsOF`s6]shmJm7dmf!4dmK3IW!!*#drW)udr;cibrW)rc
+r;cZ]!s%cW!<2Bd!<)?b!<2Ec!<;Hh!:K7Tq<e4_rpBgfmdC&SrpKacrpKacrpKacrpKacrpKac
+rpKacJaS*WJaS*WrU0[cJ,~>
+Ja.gOJa.gOs6BO_s6BO_s6BO_q<@q[qs+.\s69[dl2Uh`lM^e`l2q%O!!*#`rW)u`r;ci^rW)r_
+r;cZY!s%WO!<26`!<)3^!<29_!<;<d!:&hLq<@q[rosOblK\?Krp'I_rp'I_rp'I_rp'I_rp'I_
+rp'I_Ja.gOJa.gOrTaC_J,~>
+J`_OGJ`_OGs5s7[s5s7[s5s7[q;qYWqr[kXs5jC`jo>D\k5GA\joYVG!!*#\rW)u\r;ciZrW)r[
+r;cZU!s%KG!<2*\!<)'Z!<2-[!<;0`!9WDDq;qYWroO7^k2uXCroX1[roX1[roX1[roX1[roX1[
+roX1[J`_OGJ`_OGrT=+[J,~>
+JaS*WJaS*Ws6]jerpBjgmd:)QmJm7]mJm7bmJm7dmK`g\!!)HT!:BjdmKN[Z!!)HT!;u6e!:K7T
+rU'XcrpBgfmK!7S!!)i_rW)ob!!*#d!!*#d!W_WgrpBmhmJuYTq!J+^rpBgfmK!7S!W_WgrpBgf
+mK!7S!W_WgrpBgfmK!7S!W_WgrpBgfmK!7S!!%T<JaS*WJaWO*rr@Q~>
+Ja.gOJa.gOs69RarosRclKSBIl2UhYl2Uh^l2Uh`l3ICT!!)<L!9sR`l377R!!)<L!;u*a!:&hL
+rTX@_rosObl2^hK!!)i[rW)o^!!*#`!!*#`!W_KcrosUdl2^)Lq!%hZrosObl2^hK!W_KcrosOb
+l2^hK!W_KcrosObl2^hK!W_KcrosObl2^hK!!%T8Ja.gOJa37"rr@Q~>
+J`_OGJ`_OGs5j:]roO:_k2l[Ajo>DUjo>DZjo>D\jp1tL!!)0D!9O:\jothJ!!)0D!;ts]!9WDD
+rT4([roO7^joGDC!!)iWrW)oZ!!*#\!!*#\!W_?_roO=`joFNDpuVPVroO7^joGDC!W_?_roO7^
+joGDC!W_?_roO7^joGDC!W_?_roO7^joGDC!!%T4J`_OGJ`csorr@Q~>
+JaS*WJaS*Ws6]jerpBjgmd:)QmJm7]mKEUYmJm7Tqu?cc!<2Be!:Kab!U]sbmem.cmJm7cmed%c
+mK!7S!!)i_!s%cW!<2Bd!<2Bd!<2Bf!:Bjbmf*:]med%cmK!1Q!!)rbquHcbrrE#crrE&dquHcb
+rr@W<JaS*WJaWI(rr@Q~>
+Ja.gOJa.gOs69RarosRclKSBIl2UhYl3.1Ql2UhLqu?c_!<26a!:'I^!U9[^lMU__l2Uh_lMLV_
+l2^hK!!)i[!s%WO!<26`!<26`!<26b!9sR^lMgkYlMLV_l2^bI!!)r^quHc^rrE#_rrE&`quHc^
+rr@W8Ja.gOJa30urr@Q~>
+J`_OGJ`_OGs5j:]roO:_k2l[Ajo>DUjokbIjo>DDqu?c[!<2*]!9X1Z!TjCZk5>;[jo>D[k552[
+joGDC!!)iW!s%KG!<2*\!<2*\!<2*^!9O:Zk5PGUk552[joG>A!!)rZquHcZrrE#[rrE&\quHcZ
+rr@W4J`_OGJ`cmmrr@Q~>
+JaS*WJaS*Ws6]jerpBjgmd:)QmJm7]mJm7dmK*CV!;u6b!<2Bf!:BjbmJm7bmJm7dmK3IWmK!4R
+!!)rb!!*#d!!)i_!s%cW!<2Bd!<2Bd!<2Bf!:BjbmK*CV!;Ps^!<2Bf!:BjbmJm7bmJm7_mJm7b
+mK3IWmK!(N!!%T<JaS*WJaWL)rr@Q~>
+Ja.gOJa.gOs69RarosRclKSBIl2UhYl2Uh`l2gtN!;u*^!<26b!9sR^l2Uh^l2Uh`l2q%Ol2^eJ
+!!)r^!!*#`!!)i[!s%WO!<26`!<26`!<26b!9sR^l2gtN!;PgZ!<26b!9sR^l2Uh^l2Uh[l2Uh^
+l2q%Ol2^YF!!%T8Ja.gOJa34!rr@Q~>
+J`_OGJ`_OGs5j:]roO:_k2l[Ajo>DUjo>D\joPPF!;tsZ!<2*^!9O:Zjo>DZjo>D\joYVGjoGAB
+!!)rZ!!*#\!!)iW!s%KG!<2*\!<2*\!<2*^!9O:ZjoPPF!;P[V!<2*^!9O:Zjo>DZjo>DWjo>DZ
+joYVGjoG5>!!%T4J`_OGJ`cpnrr@Q~>
+JaS*WJaS*Ws6]jerpC0pmd:)CmJu\C!:K7TqX+=`rpBgfmK!7S!W_WgrpBgfmK!7S!W_Wgr9asn
+md:&Tmd:)CmJuYTrpBgfmK!7S!!)i_!s%cW!<2Bd!<2Bd!<2Bf!:BjdmKEUY!:K7Tq<e4_rpBgf
+mK!7S!W_WgrpBgfmK!7S!W_WgrpBgfmK!7S!W_WgrpBgfmK!7S!!%T<JaS*WJaWO*rr@Q~>
+Ja.gOJa.gOs69RarosmllKSB7l2^,7!:&hLqW\%\rosObl2^hK!W_KcrosObl2^hK!W_Kcr9=[j
+lKS?LlKSB7l2^)LrosObl2^hK!!)i[!s%WO!<26`!<26`!<26b!9sR`l3.1Q!:&hLq<@q[rosOb
+l2^hK!W_KcrosObl2^hK!W_KcrosObl2^hK!W_KcrosObl2^hK!!%T8Ja.gOJa37"rr@Q~>
+J`_OGJ`_OGs5j:]roOUhk2l[+joFQ+!9WDDqW7bXroO7^joGDC!W_?_roO7^joGDC!W_?_r8nCf
+k2lXDk2l[+joFNDroO7^joGDC!!)iW!s%KG!<2*\!<2*\!<2*^!9O:\jokbI!9WDDq;qYWroO7^
+joGDC!W_?_roO7^joGDC!W_?_roO7^joGDC!W_?_roO7^joGDC!!%T4J`_OGJ`csorr@Q~>
+JaS*WJaS*Ws6fgcrU0[crU0[cq!S+]rpKacs6]jerpBgfmdC&Ss6]jeqsOIa!U]semf*:dmf!4c
+mem.^mem.bmf!4cmf!4cmf!4dmJm7dmJm7`mJm7dmK*CVmf!4cmf!4cmf!4cmf!4cmf!4cmf!4c
+mf!3;mXaeWmXag)mf*9;~>
+Ja.gOJa.gOs6BO_rTaC_rTaC_q!.hYrp'I_s69RarosOblK\?Ks69Raqs+1]!U9[alMgk`lM^e_
+lMU_ZlMU_^lM^e_lM^e_lM^e`l2Uh`l2Uh\l2Uh`l2gtNlM^e_lM^e_lM^e_lM^e_lM^e_lM^e_
+lM^d7l@J5Ol@J7!lMgj7~>
+J`_OGJ`_OGs5s7[rT=+[rT=+[pu_PUroX1[s5j:]roO7^k2uXCs5j:]qr[nY!TjC]k5PG\k5GA[
+k5>;Vk5>;Zk5GA[k5GA[k5GA\jo>D\jo>DXjo>D\joPPFk5GA[k5GA[k5GA[k5GA[k5GA[k5GA[
+k5G@3k(2ZGk(2[nk5PF3~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)oarrE&d!!)$H!!)rb!!)ucrW)]\!!*#dquHcbrW)3N!!)TX
+!!(^?!<9e$rr@Q~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)o]rrE&`!!)$D!!)r^!!)u_rW)]X!!*#`quHc^rW)3J!!)TT
+!!(^;!!'aurr@Q~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)oYrrE&\!!)$@!!)rZ!!)u[rW)]T!!*#\quHcZrW)3F!!)TP
+!!(^7!!'aqrr@Q~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)l`!!*#d!!)'I!W_WgrpBpimJu\C!<2Bd!;Z$a!:BjamK*CV
+!<2Bd!:&tP!;Z$_!;l0a!86c?s1[q$!.Y~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)l\!!*#`!!)'E!W_KcrosXel2^,7!<26`!;Ym]!9sR]l2gtN
+!<26`!:&hL!;Ym[!;l$]!86W;!5@au!.Y~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)lX!!*#\!!)'A!W_?_roO@ajoFQ+!<2*\!;YaY!9O:YjoPPF
+!<2*\!:&\H!;YaW!;kmY!86K7!5@Uq!.Y~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)l`!!*#d!!)*J!!*#d!W_WgrpBgfmK!7S!!)l`!!*#d!!)uc
+!s%cW!<2Bd!:&tP!;Z$_!;l0a!86c?s1[q$!.Y~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)l\!!*#`!!)*F!!*#`!W_KcrosObl2^hK!!)l\!!*#`!!)u_
+!s%WO!<26`!:&hL!;Ym[!;l$]!86W;!5@au!.Y~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)lX!!*#\!!)*B!!*#\!W_?_roO7^joGDC!!)lX!!*#\!!)u[
+!s%KG!<2*\!:&\H!;YaW!;kmY!86K7!5@Uq!.Y~>
+JaS*WJaS*WJaS*WJaU2=r;cfa!!*#d#lsD]!:K7TmJm7dmf!4cmf!4^mJm7dmK*CV!<2Bf!:Bjd
+mJm7`mJm7dmJm7dmJm7`mJm7`mK3IW!!)f^r;clcrW)udr;b@8!<9e$rr@Q~>
+Ja.gOJa.gOJa.gOJa0o5r;cf]!!*#`#ls8U!:&hLl2Uh`lM^e_lM^eZl2Uh`l2gtN!<26b!9sR`
+l2Uh\l2Uh`l2Uh`l2Uh\l2Uh\l2q%O!!)fZr;cl_rW)u`r;b@4!!'aurr@Q~>
+J`_OGJ`_OGJ`_OGJ`aW-r;cfY!!*#\#ls,M!9WDDjo>D\k5GA[k5GAVjo>D\joPPF!<2*^!9O:\
+jo>DXjo>D\jo>D\jo>DXjo>DXjoYVG!!)fVr;cl[rW)u\r;b@0!!'aqrr@Q~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d!W_WgrpKdds6]pgmK!7S!W_WgrpBadqX+=`rpBgf
+mK!7S!W_ZVrW)c^!!*#d!W_ZVrW)la!!)i_rrE)e!!)l`!!*#d!!)rb!s%cW!7L98s1[q$!.Y~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`!W_Kcrp'L`s69Xcl2^hK!W_KcrosI`qW\%\rosOb
+l2^hK!W_NNrW)cZ!!*#`!W_NNrW)l]!!)i[rrE)a!!)l\!!*#`!!)r^!s%WO!7L-4!5@au!.Y~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\!W_?_roX4\s5j@_joGDC!W_?_roO1\qW7bXroO7^
+joGDC!W_BFrW)cV!!*#\!W_BFrW)lY!!)iWrrE)]!!)lX!!*#\!!)rZ!s%KG!7L!0!5@Uq!.Y~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#drrE#c!!*#d!W_WgrpBmhmd:&Tq!J+^rpBgfmK!7S
+!W_WgrpBadqX+=`rpBadr9aObrpBadq!J+^p?hn\rpBgfmdC#Rs6]jefC&A4_s[O)J,~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`rrE#_!!*#`!W_KcrosUdlKS?Lq!%hZrosObl2^hK
+!W_KcrosI`qW\%\rosI`r9=7^rosI`q!%hZp?DVXrosOblK\<Js69RafBW&0_s77%J,~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\rrE#[!!*#\!W_?_roO=`k2lXDpuVPVroO7^joGDC
+!W_?_roO1\qW7bXroO1\r8mtZroO1\puVPVp>u>TroO7^k2uUBs5j:]fB2c,_rgt!J,~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d!W_WgrpBadrpBgfmK!7S!!)uc!!)i_!!*#d!W_Wg
+rpBgfmK!7S!!)l`!!*#d!!)rb!s%cW!;Gm]!;>g\!<2Bf!:BjdmK3IWmJt]'!<9e$rr@Q~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`!W_KcrosI`rosObl2^hK!!)u_!!)i[!!*#`!W_Kc
+rosObl2^hK!!)l\!!*#`!!)r^!s%WO!;GaY!;>[X!<26b!9sR`l2q%Ol2]8t!!'aurr@Q~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\!W_?_roO1\roO7^joGDC!!)u[!!)iW!!*#\!W_?_
+roO7^joGDC!!)lX!!*#\!!)rZ!s%KG!;GUU!;>OT!<2*^!9O:\joYVGjoEil!!'aqrr@Q~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d"p")Z!:K7TrpBgfmK!7S!W_WgrpBadq<e:amK!7S
+"T[rjmd:)SmJm7dmf*:dmKEUY!:K7TrpBgfmJutK!!)l`!!*#d!!*#d$io\qmd:&Tmd:)CmJtf*
+!<9e$rr@Q~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`"p!rR!:&hLrosObl2^hK!W_KcrosI`q<A"]l2^hK
+"T[fflKSBKl2Uh`lMgk`l3.1Q!:&hLrosObl2^PC!!)l\!!*#`!!*#`$ioPmlKS?LlKSB7l2]B"
+!!'aurr@Q~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\"p!fJ!9WDDroO7^joGDC!W_?_roO1\q;q_YjoGDC
+"T[Zbk2l[Cjo>D\k5PG\jokbI!9WDDroO7^joG,;!!)lX!!*#\!!*#\$ioDik2lXDk2l[+joEro
+!!'aqrr@Q~>
+JaS*WJaS*WJaS*WJaU2=r;cibrW)ud!!*#d!W_WgrpBgfmdC&SrpKacp[/"]r9aObrU0XbrU'Xc
+r9aObrU0Xbs6fdbqX+=`qsOF`rpK^bs6fme!U]semf*::mK!:$mf*9;~>
+Ja.gOJa.gOJa.gOJa0o5r;ci^rW)u`!!*#`!W_KcrosOblK\?Krp'I_pZ__Yr9=7^rTa@^rTX@_
+r9=7^rTa@^s6BL^qW\%\qs+.\rp'F^s6BUa!U9[alMgk6l2UgulMgj7~>
+J`_OGJ`_OGJ`_OGJ`aW-r;ciZrW)u\!!*#\!W_?_roO7^k2uXCroX1[pZ;GUr8mtZrT=(ZrT4([
+r8mtZrT=(Zs5s4ZqW7bXqr[kXroX.Zs5s=]!TjC]k5PG2jo>Cqk5PF3~>
+JaS*WJaS*WJaS*WJaSonquH-P!!)?QquHN[!!'k'!<9e$rr@Q~>
+Ja.gOJa.gOJa.gOJa/WfquH-L!!)?MquHNW!!'k#!!'aurr@Q~>
+J`_OGJ`_OGJ`_OGJ``?^quH-H!!)?IquHNS!!'jt!!'aqrr@Q~>
+JaS*WJaS*WJaS*WJaS*WRI1(T^@)"$J,~>
+Ja.gOJa.gOJa.gOJa.gORHabP^?Y^uJ,~>
+J`_OGJ`_OGJ`_OGJ`_OGRH=JL^?5FqJ,~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)oarrE&d!!)$H!!)rb!!)ucrW)]\!!)rb!!)rb!!)6N!!)TX
+!!(mD!!)f^!!'q)rr@Q~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)o]rrE&`!!)$D!!)r^!!)u_rW)]X!!)r^!!)r^!!)6J!!)TT
+!!(m@!<;iZ!<9t%rr@Q~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)oYrrE&\!!)$@!!)rZ!!)u[rW)]T!!)rZ!!)rZ!!)6F!!)TP
+!!(m<!!)fV!!'q!rr@Q~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)l`!!*#d!!)'I!W_WgrpBpimJu\C!<2Bd!;Z$a!:BjdmK*CV
+!<2Bf!:BjOmJm7_mJm7amJm7CmJm7`mJm7(mf*9;~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)l\!!*#`!!)'E!W_KcrosXel2^,7!<26`!;Ym]!9sR`l2gtN
+!<26b!9sRKl2Uh[l2Uh]l2Uh?l2^k\l2^k$lMgj7~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)lX!!*#\!!)'A!W_?_roO@ajoFQ+!<2*\!;YaY!9O:\joPPF
+!<2*^!9O:Gjo>DWjo>DYjo>D;jo>DXjo>Cuk5PF3~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)l`!!*#d!!)*J!!*#d!W_WgrpBgfmK!7S!!)l`!!*#d!W_Wg
+rpBgfmK!7S!!)<P!!)i_!!)oa!!(gB!!)rb!!'k'rr@Q~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)l\!!*#`!!)*F!!*#`!W_KcrosObl2^hK!!)l\!!*#`!W_Kc
+rosObl2^hK!!)<L!!)i[!!)o]!!(g>!<;u^!<9n#rr@Q~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)lX!!*#\!!)*B!!*#\!W_?_roO7^joGDC!!)lX!!*#\!W_?_
+roO7^joGDC!!)<H!!)iW!!)oY!!(g:!!)rZ!!'jtrr@Q~>
+JaS*WJaS*WJaS*WJaU2=r;cfa!!*#d#lsD]!:K7TmJm7dmf!4cmf!4^mJm7dmK*CV!<2Bf!:Bjd
+mJm7`mJm7dmK*CV!<2Bf!:BjdmJm7`mK3IW!!)f^r;clcrW)udr;bF:!!*#d!!'h&rr@Q~>
+Ja.gOJa.gOJa.gOJa0o5r;cf]!!*#`#ls8U!:&hLl2Uh`lM^e_lM^eZl2Uh`l2gtN!<26b!9sR`
+l2Uh\l2Uh`l2gtN!<26b!9sR`l2Uh\l2q%O!!)fZr;cl_rW)u`r;bF6!<<&`!<9k"rr@Q~>
+J`_OGJ`_OGJ`_OGJ`aW-r;cfY!!*#\#ls,M!9WDDjo>D\k5GA[k5GAVjo>D\joPPF!<2*^!9O:\
+jo>DXjo>D\joPPF!<2*^!9O:\jo>DXjoYVG!!)fVr;cl[rW)u\r;bF2!!*#\!!'gsrr@Q~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d!W_WgrpKdds6]pgmK!7S!W_WgrpBadqX+=`rpBgf
+mK!7S!W_ZVrW)c^!!*#d!W_WgrpBgfmK!7S!!)l`rrE)e!!)l`!!*#d!!)rb!s%cW!7U?;!:Bj%
+mf*9;~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`!W_Kcrp'L`s69Xcl2^hK!W_KcrosI`qW\%\rosOb
+l2^hK!W_NNrW)cZ!!*#`!W_KcrosObl2^hK!!)l\rrE)a!!)l\!!*#`!!)r^!s%WO!7U37s6BX!
+lMgj7~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\!W_?_roX4\s5j@_joGDC!W_?_roO1\qW7bXroO7^
+joGDC!W_BFrW)cV!!*#\!W_?_roO7^joGDC!!)lXrrE)]!!)lX!!*#\!!)rZ!s%KG!7U'3!9O9r
+k5PF3~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#drrE#c!!*#d!W_WgrpBmhmd:&Tq!J+^rpBgfmK!7S
+!W_WgrpBadqX+=`rpBgfmK!7S!W_WgrpBadqX+=`p?hn\rpBgfmdC#Rs6]jedd@)8^@)"$J,~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`rrE#_!!*#`!W_KcrosUdlKS?Lq!%hZrosObl2^hK
+!W_KcrosI`qW\%\rosObl2^hK!W_KcrosI`qW\%\p?DVXrosOblK\<Js69Radcpi4^?Y^uJ,~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\rrE#[!!*#\!W_?_roO=`k2lXDpuVPVroO7^joGDC
+!W_?_roO1\qW7bXroO7^joGDC!W_?_roO1\qW7bXp>u>TroO7^k2uUBs5j:]dcLN0^?5FqJ,~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d!W_WgrpBadrpBgfmK!7S!!)uc!!)i_!!*#d!W_Wg
+rpBgfmK!7S!!)l`!!*#d!W_WgrpBgfmK!7S!!)l`!!)`\!!*#d!W_WgrpBjgmd:)(mK*CV!5It%
+!.Y~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`!W_KcrosI`rosObl2^hK!!)u_!!)i[!!*#`!W_Kc
+rosObl2^hK!!)l\!!*#`!W_KcrosObl2^hK!!)l\!!)`X!!*#`!W_KcrosRclKSAul2q"Ns1dk!
+!.Y~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\!W_?_roO1\roO7^joGDC!!)u[!!)iW!!*#\!W_?_
+roO7^joGDC!!)lX!!*#\!W_?_roO7^joGDC!!)lX!!)`T!!*#\!W_?_roO:_k2lZmjoPPF!5I[r
+!.Y~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d"p")Z!:K7TrpBgfmK!7S!W_WgrpBadq<e:amK!7S
+"T[rjmd:)SmJm7dmf*:dmK*CV!<2Bf!:BjdmK*CV!;Z$_!;c*`!<2Bd!<2Bp!:BjTmJm7TmJu\C
+!8$W=!<2Bd!5S%&!.Y~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`"p!rR!:&hLrosObl2^hK!W_KcrosI`q<A"]l2^hK
+"T[fflKSBKl2Uh`lMgk`l2gtN!<26b!9sR`l2gtN!;Ym[!;bs\!<26`!<26l!9sRLl2UhLl2^,7
+!8$K9s8M9`s1mq"!.Y~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\"p!fJ!9WDDroO7^joGDC!W_?_roO1\q;q_YjoGDC
+"T[Zbk2l[Cjo>D\k5PG\joPPF!<2*^!9O:\joPPF!;YaW!;bgX!<2*\!<2*h!9O:Djo>DDjoFQ+
+!8$?5!<2*\!5Ras!.Y~>
+JaS*WJaS*WJaS*WJaU2=r;cibrW)ud!!*#d!W_WgrpBgfmdC&SrpKacp[/"]r9aObrU0XbrU'Xc
+r9aObr9aObr9aObq!J+^qsOF`rpK^bs6fme!U]semf*:=mJm7bmJm7'mf*9;~>
+Ja.gOJa.gOJa.gOJa0o5r;ci^rW)u`!!*#`!W_KcrosOblK\?Krp'I_pZ__Yr9=7^rTa@^rTX@_
+r9=7^r9=7^r9=7^q!%hZqs+.\rp'F^s6BUa!U9[alMgk9l2^k^l2^k#lMgj7~>
+J`_OGJ`_OGJ`_OGJ`aW-r;ciZrW)u\!!*#\!W_?_roO7^k2uXCroX1[pZ;GUr8mtZrT=(ZrT4([
+r8mtZr8mtZr8mtZpuVPVqr[kXroX.Zs5s=]!TjC]k5PG5jo>DZjo>Ctk5PF3~>
+JaS*WJaS*WJaS*WJaSonquH-P!!)?QquHN[!!("+!!)l`!!'n(rr@Q~>
+Ja.gOJa.gOJa.gOJa/WfquH-L!!)?MquHNW!!("'!<;o\!<9q$rr@Q~>
+J`_OGJ`_OGJ`_OGJ``?^quH-H!!)?IquHNS!!("#!!)lX!!'murr@Q~>
+JaS*WJaS*WJaS*WJaS*WT'cRYq!J+^_s[O)J,~>
+Ja.gOJa.gOJa.gOJa.gOT'?=Uq!%kZ_s77%J,~>
+J`_OGJ`_OGJ`_OGJ`_OGT&p"QpuVPV_rgt!J,~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)oarrE&d!!)$H!!)rb!!)ucrW)]\!!)rb!!)rb!!)6N!!)TX
+!!(mD!!)uc!!)uc!!'q)rr@Q~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)o]rrE&`!!)$D!!)r^!!)u_rW)]X!!)r^!!)r^!!)6J!!)TT
+!!(m@!!)u_!!)u_!!'q%rr@Q~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)oYrrE&\!!)$@!!)rZ!!)u[rW)]T!!)rZ!!)rZ!!)6F!!)TP
+!!(m<!<<#[!<<#[!<9t!rr@Q~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)l`!!*#d!!)'I!W_WgrpBpimJu\C!<2Bd!;Z$a!:BjdmK*CV
+!<2Bf!:BjOmJm7_mJm7amJm7CmJm7dmJm7dmJm7(mf*9;~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)l\!!*#`!!)'E!W_KcrosXel2^,7!<26`!;Ym]!9sR`l2gtN
+!<26b!9sRKl2Uh[l2Uh]l2Uh?l2Uh`l2Uh`l2Uh$lMgj7~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)lX!!*#\!!)'A!W_?_roO@ajoFQ+!<2*\!;YaY!9O:\joPPF
+!<2*^!9O:Gjo>DWjo>DYjo>D;joGG\joGG\joGFuk5PF3~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)l`!!*#d!!)*J!!*#d!W_WgrpBgfmK!7S!!)l`!!*#d!W_Wg
+rpBgfmK!7S!!)<P!!)i_!!)oa!!(gB"p")Z!:K7T_=%='J,~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)l\!!*#`!!)*F!!*#`!W_KcrosObl2^hK!!)l\!!*#`!W_Kc
+rosObl2^hK!!)<L!!)i[!!)o]!!(g>"p!rR!:&hL_<V%#J,~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)lX!!*#\!!)*B!!*#\!W_?_roO7^joGDC!!)lX!!*#\!W_?_
+roO7^joGDC!!)<H!!)iW!!)oY!!(g:#63iJs5rJD_<1atJ,~>
+JaS*WJaS*WJaS*WJaU2=r;cfa!!*#d#lsD]!:K7TmJm7dmf!4cmf!4^mJm7dmK*CV!<2Bf!:Bjd
+mJm7`mJm7dmK*CV!<2Bf!:BjdmJm7`mJm7dmJm7_mem.cmf!4dmem.:mK<OX!:Bj&mf*9;~>
+Ja.gOJa.gOJa.gOJa0o5r;cf]!!*#`#ls8U!:&hLl2Uh`lM^e_lM^eZl2Uh`l2gtN!<26b!9sR`
+l2Uh\l2Uh`l2gtN!<26b!9sR`l2Uh\l2Uh`l2Uh[lMU__lM^e`lMU_6l3%+P!9sR"lMgj7~>
+J`_OGJ`_OGJ`_OGJ`aW-r;cfY!!*#\#ls,M!9WDDjo>D\k5GA[k5GAVjo>D\joPPF!<2*^!9O:\
+jo>DXjo>D\joPPF!<2*^!9O:\jo>DXjo>D\jo>DWk5>;[k5GA\k5>;2jok_Hs5s?sk5PF3~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d!W_WgrpKdds6]pgmK!7S!W_WgrpBadqX+=`rpBgf
+mK!7S!W_ZVrW)c^!!*#d!W_WgrpBgfmK!7S!!)l`!!*#d!!)l`!!*#d!!)rb!s%cW!7UB8!5It%
+!.Y~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`!W_Kcrp'L`s69Xcl2^hK!W_KcrosI`qW\%\rosOb
+l2^hK!W_NNrW)cZ!!*#`!W_KcrosObl2^hK!!)l\!!*#`!!)l\!!*#`!!)r^!s%WO!7U64!5Ih!
+!.Y~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\!W_?_roX4\s5j@_joGDC!W_?_roO1\qW7bXroO7^
+joGDC!W_BFrW)cV!!*#\!W_?_roO7^joGDC!!)lX!!*#\!!)lX!!*#\!!)rZ!s%KG!7U*0s1d^r
+!.Y~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#drrE#c!!*#d!W_WgrpBmhmd:&Tq!J+^rpBgfmK!7S
+!W_WgrpBadqX+=`rpBgfmK!7S!W_WgrpBadqX+IdmJuYTqX+=`rpBgfmdC#Rs6]jefC&>4_s[O)
+J,~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`rrE#_!!*#`!W_KcrosUdlKS?Lq!%hZrosObl2^hK
+!W_KcrosI`qW\%\rosObl2^hK!W_KcrosI`qW\1`l2^)LqW\%\rosOblK\<Js69RafBW&0_s77%
+J,~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\rrE#[!!*#\!W_?_roO=`k2lXDpuVPVroO7^joGDC
+!W_?_roO1\qW7bXroO7^joGDC!W_?_roO1\qW7n\joFNDqW7bXroO7^k2uUBs5j:]fB2f,_rgt!
+J,~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d!W_WgrpBadrpBgfmK!7S!!)uc!!)i_!!*#d!W_Wg
+rpBgfmK!7S!!)l`!!*#d!W_WgrpBgfmK!7S!!)l`"9@iimK!+O!!*#d!W_WgrpBjgmd:)(mf!4$
+mf*9;~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`!W_KcrosI`rosObl2^hK!!)u_!!)i[!!*#`!W_Kc
+rosObl2^hK!!)l\!!*#`!W_KcrosObl2^hK!!)l\"9@]el2^\G!!*#`!W_KcrosRclKSAulM^du
+lMgj7~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\!W_?_roO1\roO7^joGDC!!)u[!!)iW!!*#\!W_?_
+roO7^joGDC!!)lX!!*#\!W_?_roO7^joGDC!!)lX"9@QajoG8?!!*#\!W_?_roO:_k2lZmk5PCq
+k5PF3~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d"p")Z!:K7TrpBgfmK!7S!W_WgrpBadq<e:amK!7S
+"T[rjmd:)SmJm7dmf*:dmK*CV!<2Bf!:BjdmK*CV!;Z$c!:BjT!<2Bd!<2Bd!<2Bp!:BjTmJm7T
+mJu\C!8$WA!:BjT!5S%&!.Y~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`"p!rR!:&hLrosObl2^hK!W_KcrosI`q<A"]l2^hK
+"T[fflKSBKl2Uh`lMgk`l2gtN!<26b!9sR`l2gtN!;Ym_!9sRL!<26`!<26`!<26l!9sRLl2UhL
+l2^,7!8$K=!9sRL!5Rn"!.Y~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\"p!fJ!9WDDroO7^joGDC!W_?_roO1\q;q_YjoGDC
+"T[Zbk2l[Cjo>D\k5PG\joPPF!<2*^!9O:\joPPF!;Ya[!9O:D!<2*\!<2*\!<2*h!9O:Djo>DD
+joFQ+!8$?9s5s@Ds1mds!.Y~>
+JaS*WJaS*WJaS*WJaU2=r;cibrW)ud!!*#d!W_WgrpBgfmdC&SrpKacp[/"]r9aObrU0XbrU'Xc
+r9aObr9aObr9aObp[/(_mK!7SrW)rcr;clcrr<)f!<2Ed!8$WC!:K7Tmd:(kmf*9;~>
+Ja.gOJa.gOJa.gOJa0o5r;ci^rW)u`!!*#`!W_KcrosOblK\?Krp'I_pZ__Yr9=7^rTa@^rTX@_
+r9=7^r9=7^r9=7^pZ_e[l2^hKrW)r_r;cl_rr<)b!<29`!8$K?!:&hLlKSAclMgj7~>
+J`_OGJ`_OGJ`_OGJ`aW-r;ciZrW)u\!!*#\!W_?_roO7^k2uXCroX1[pZ;GUr8mtZrT=(ZrT4([
+r8mtZr8mtZr8mtZpZ;MWjoGDCrW)r[r;cl[rr<)^!<2-\!8$?;s5rJDk2u][k5PF3~>
+JaS*WJaS*WJaS*WJaSonquH-P!!)?QquHN[!!("+!!*#d!!*#d!!'n(rr@Q~>
+Ja.gOJa.gOJa.gOJa/WfquH-L!!)?MquHNW!!("'!!*#`!!*#`!!'n$rr@Q~>
+J`_OGJ`_OGJ`_OGJ``?^quH-H!!)?IquHNS!!("#!<<&\!<<&\!<9purr@Q~>
+JaS*WJaS*WJaS*WJaS*WT'cRYrU'XcrU'Xc_s[O)J,~>
+Ja.gOJa.gOJa.gOJa.gOT'?:UrTX@_rTX@__s77%J,~>
+J`_OGJ`_OGJ`_OGJ`_OGT&p%QrT4+[rT4+[_rgt!J,~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)oarrE&d!!)$H!!)rb!!)ucrW)]\!!*#dquHcbrW)3N!!)TX
+!!(mDpA`%urr@Q~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)o]rrE&`!!)$D!!)r^!!)u_rW)]X!!*#`quHc^rW)3J!!)TT
+!!(m@p&N"qrr@Q~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)oYrrE&\!!)$@!!)rZ!!)u[rW)]T!!*#\quHcZrW)3F!!)TP
+!!(m<pA`%mrr@Q~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)l`!!*#d!!)'I!W_WgrpBpimJu\C!<2Bd!;Z$a!:BjamK*CV
+!<2Bd!:&tP!;Z$_!;l0a!8d,Ds7l!^s24:)!.Y~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)l\!!*#`!!)'E!W_KcrosXel2^,7!<26`!;Ym]!9sR]l2gtN
+!<26`!:&hL!;Ym[!;l$]!8cu@!;PgZ!5n+%!.Y~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)lX!!*#\!!)'A!W_?_roO@ajoFQ+!<2*\!;YaY!9O:YjoPPF
+!<2*\!:&\H!;YaW!;kmY!8ci<s7k^Vs24"!!.Y~>
+JaS*WJaS*WJaS*WJaU>A!!*#d!!)l`!!*#d!!)*J!!*#d!W_WgrpBgfmK!7S!!)l`!!*#d!!)uc
+!s%cW!<2Bd!:&tP!;Z$_!;l0a!8d,Ds7l!^s24:)!.Y~>
+Ja.gOJa.gOJa.gOJa1&9!!*#`!!)l\!!*#`!!)*F!!*#`!W_KcrosObl2^hK!!)l\!!*#`!!)u_
+!s%WO!<26`!:&hL!;Ym[!;l$]!8cu@!;PgZ!5n+%!.Y~>
+J`_OGJ`_OGJ`_OGJ`ac1!!*#\!!)lX!!*#\!!)*B!!*#\!W_?_roO7^joGDC!!)lX!!*#\!!)u[
+!s%KG!<2*\!:&\H!;YaW!;kmY!8ci<s7k^Vs24"!!.Y~>
+JaS*WJaS*WJaS*WJaU2=r;cfa!!*#d#lsD]!:K7TmJm7dmf!4cmf!4^mJm7dmK*CV!<2Bf!:Bjd
+mJm7`mJm7dmJm7dmJm7`mJm7`mJm7dmJm7_mem.cmf!4dmem.=mK!:^mK!:)mf*9;~>
+Ja.gOJa.gOJa.gOJa0o5r;cf]!!*#`#ls8U!:&hLl2Uh`lM^e_lM^eZl2Uh`l2gtN!<26b!9sR`
+l2Uh\l2Uh`l2Uh`l2Uh\l2Uh\l2Uh`l2Uh[lMU__lM^e`lMU_9l2UhZl2Uh%lMgj7~>
+J`_OGJ`_OGJ`_OGJ`aW-r;cfY!!*#\#ls,M!9WDDjo>D\k5GA[k5GAVjo>D\joPPF!<2*^!9O:\
+jo>DXjo>D\jo>D\jo>DXjo>DXjo>D\jo>DWk5>;[k5GA\k5>;5joGGVjoGG!k5PF3~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d!W_WgrpKdds6]pgmK!7S!W_WgrpBadqX+=`rpBgf
+mK!7S!W_ZVrW)c^!!*#d!W_ZVrW)la!!)i_!!*#d!!)l`!!*#d!!)rb!s%cW!8$W=s7l!^s24:)
+!.Y~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`!W_Kcrp'L`s69Xcl2^hK!W_KcrosI`qW\%\rosOb
+l2^hK!W_NNrW)cZ!!*#`!W_NNrW)l]!!)i[!!*#`!!)l\!!*#`!!)r^!s%WO!8$K9!;PgZ!5n+%
+!.Y~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\!W_?_roX4\s5j@_joGDC!W_?_roO1\qW7bXroO7^
+joGDC!W_BFrW)cV!!*#\!W_BFrW)lY!!)iW!!*#\!!)lX!!*#\!!)rZ!s%KG!8$?5s7k^Vs24"!
+!.Y~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#drrE#c!!*#d!W_WgrpBmhmd:&Tq!J+^rpBgfmK!7S
+!W_WgrpBadqX+=`rpBadr9aObrpBadq!J7bmJuYTqX+=`rpBgfmdC#Rs6]jefBrY=rU'[crU'[c
+_s[O)J,~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`rrE#_!!*#`!W_KcrosUdlKS?Lq!%hZrosObl2^hK
+!W_KcrosI`qW\%\rosI`r9=7^rosI`q!%t^l2^)LqW\%\rosOblK\<Js69RafBN>9rTX@_rTX@_
+_s77%J,~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\rrE#[!!*#\!W_?_roO=`k2lXDpuVPVroO7^joGDC
+!W_?_roO1\qW7bXroO1\r8mtZroO1\puV\ZjoFNDqW7bXroO7^k2uUBs5j:]fB*)5rT4+[rT4+[
+_rgt!J,~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d!W_WgrpBadrpBgfmK!7S!!)uc!!)i_!!*#d!W_Wg
+rpBgfmK!7S!!)l`!!*#d!!)rb!s%cW!;Gma!:BjT!;c*`!<2Bf!:BjdmK3IWmJtl,!<;i^!<9t)
+rr@Q~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`!W_KcrosI`rosObl2^hK!!)u_!!)i[!!*#`!W_Kc
+rosObl2^hK!!)l\!!*#`!!)r^!s%WO!;Ga]!9sRL!;bs\!<26b!9sR`l2q%Ol2]H$!!)fZ!!'q%
+rr@Q~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\!W_?_roO1\roO7^joGDC!!)u[!!)iW!!*#\!W_?_
+roO7^joGDC!!)lX!!*#\!!)rZ!s%KG!;GUY!9O:D!;bgX!<2*^!9O:\joYVGjoF#q!<;iV!<9t!
+rr@Q~>
+JaS*WJaS*WJaS*WJaU2=!!*#d!!*#d!!*#d"p")Z!:K7TrpBgfmK!7S!W_WgrpBadq<e:amK!7S
+"T[rjmd:)SmJm7dmf*:dmKEUY!:K7TrpBgfmJutK"9@iimK!7S!!*#d!!*#d$io\qmd:&Tmd:)C
+mJtu/!<;i^!<9t)rr@Q~>
+Ja.gOJa.gOJa.gOJa0o5!!*#`!!*#`!!*#`"p!rR!:&hLrosObl2^hK!W_KcrosI`q<A"]l2^hK
+"T[fflKSBKl2Uh`lMgk`l3.1Q!:&hLrosObl2^PC"9@]el2^hK!!*#`!!*#`$ioPmlKS?LlKSB7
+l2]Q'!!)fZ!!'q%rr@Q~>
+J`_OGJ`_OGJ`_OGJ`aW-!!*#\!!*#\!!*#\"p!fJ!9WDDroO7^joGDC!W_?_roO1\q;q_YjoGDC
+"T[Zbk2l[Cjo>D\k5PG\jokbI!9WDDroO7^joG,;"9@QajoGDC!!*#\!!*#\$ioDik2lXDk2l[+
+joF,t!<;iV!<9t!rr@Q~>
+JaS*WJaS*WJaS*WJaU2=r;cibrW)ud!!*#d!W_WgrpBgfmdC&SrpKacp[/"]r9aObrU0XbrU'Xc
+r9aObrU0Xbs6fdbq<e:amK!7SrW)rcr;clcrr<)f!<2Ed!86c?s7l!^s24:)!.Y~>
+Ja.gOJa.gOJa.gOJa0o5r;ci^rW)u`!!*#`!W_KcrosOblK\?Krp'I_pZ__Yr9=7^rTa@^rTX@_
+r9=7^rTa@^s6BL^q<A"]l2^hKrW)r_r;cl_rr<)b!<29`!86W;!;PgZ!5n+%!.Y~>
+J`_OGJ`_OGJ`_OGJ`aW-r;ciZrW)u\!!*#\!W_?_roO7^k2uXCroX1[pZ;GUr8mtZrT=(ZrT4([
+r8mtZrT=(Zs5s4Zq;q_YjoGDCrW)r[r;cl[rr<)^!<2-\!86K7s7k^Vs24"!!.Y~>
+JaS*WJaS*WJaS*WJaSonquH-P!!)?QquHN[!!(%,!<;i^!<9t)rr@Q~>
+Ja.gOJa.gOJa.gOJa/WfquH-L!!)?MquHNW!!(%(!!)fZ!!'q%rr@Q~>
+J`_OGJ`_OGJ`_OGJ``?^quH-H!!)?IquHNS!!(%$!<;iV!<9t!rr@Q~>
+JaS*WJaS*WJaS*WJaS*WT'l=P_s[O)J,~>
+Ja.gOJa.gOJa.gOJa.gOT'H"L_s77%J,~>
+J`_OGJ`_OGJ`_OGJ`_OGT'#bH_rgt!J,~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+JaS*WJaS*WJaS*WJaS*WJaV(Vrr@Q~>
+Ja.gOJa.gOJa.gOJa.gOJa1eNrr@Q~>
+J`_OGJ`_OGJ`_OGJ`_OGJ`bMFrr@Q~>
+NpZlIrU0XbrU'Xcr9aObJaS*WJaS*WJaS*WJaS*Wg[>(AJ,~>
+Np6TErTa@^rTX@_r9=7^Ja.gOJa.gOJa.gOJa.gOgZne=J,~>
+Nog<ArT=(ZrT4([r8mtZJ`_OGJ`_OGJ`_OGJ`_OGgZJM9J,~>
+O7*#JrpBadrpBpimd:)C!<2Bf!:Bi<mXaeWmXaeWmXaeWmXaf]mf*9;~>
+O6Z`FrosI`rosXelKSB7!<26b!9sQ8l@J5Ol@J5Ol@J5Ol@J6UlMgj7~>
+O66HBroO1\roO@ak2l[+!<2*^!9O94k(2ZGk(2ZGk(2ZGk(2[Mk5PF3~>
+OR</MmK!7S!!*#d!W_WgrpBgfmK!7S!!%T<JaS*WJaS*WJaS*WJaV@^rr@Q~>
+OQllIl2^hK!!*#`!W_KcrosObl2^hK!!%T8Ja.gOJa.gOJa.gOJa2(Vrr@Q~>
+OQHTEjoGDC!!*#\!W_?_roO7^joGDC!!%T4J`_OGJ`_OGJ`_OGJ`beNrr@Q~>
+NpZlIqX+CbmK!7S!W_WgrpBadJaS*WJaS*WJaS*WJaS*Wh<t:CJ,~>
+Np6TEqW\+^l2^hK!W_KcrosI`Ja.gOJa.gOJa.gOJa.gOh<P"?J,~>
+Nog<AqW7hZjoGDC!W_?_roO1\J`_OGJ`_OGJ`_OGJ`_OGh<+_;J,~>
+NpZlIqsFOdmd:)SmK*CV!<2Bd!:]FQ!.jQhmXaeWmXaeWmXaeWme-#J!.Y~>
+Np6TEqs"7`lKSBKl2gtN!<26`!:]:M!.jEdl@J5Ol@J5Ol@J5OlLjHB!.Y~>
+Nog<AqrRt\k2l[CjoPPF!<2*\!:].I!.j9`k(2ZGk(2ZGk(2ZGk4Rm:!.Y~>
+NpZlIr9aObrpBadrpBgfmK!7S!!)QWq#H!6JaS*WJaS*WJaS*WJaW4!rr@Q~>
+Np6TEr9=7^rosI`rosObl2^hK!!)QSq#H!2Ja.gOJa.gOJa.gOJa2pnrr@Q~>
+Nog<Ar8mtZroO1\roO7^joGDC!!)QOq#H!.J`_OGJ`_OGJ`_OGJ`cXfrr@Q~>
+NpZlIrU'XcrU'XcrpBgfmK!7S!!)QWrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Np6TErTX@_rTX@_rosObl2^hK!!)QSrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+Nog<ArT4([rT4([roO7^joGDC!!)QOrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+NpZlIrpBadqsFLcmK!7S!W_WgnF$;VJaS*WJaS*WJaS*WJaS*Wn*^2UJ,~>
+Np6TErosI`qs"4_l2^hK!W_KcnEU#RJa.gOJa.gOJa.gOJa.gOn*9oQJ,~>
+Nog<AroO1\qrRq[joGDC!W_?_nE0`NJ`_OGJ`_OGJ`_OGJ`_OGn)jWMJ,~>
+ORE#H!:KabrpBadr9aObn*^2UJaS*WJaS*WJaS*WJaS*Wn*^2UJ,~>
+OQu`D!:'I^rosI`r9=7^n*9oQJa.gOJa.gOJa.gOJa.gOn*9oQJ,~>
+OQQH@!9X1ZroO1\r8mtZn)jWMJ`_OGJ`_OGJ`_OGJ`_OGn)jWMJ,~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WJaS*WJaS*WJaW!prr@Q~>
+Ja2Lbrr@W8Ja.gOJa.gOJa.gOJa2^hrr@Q~>
+J`c4Zrr@W4J`_OGJ`_OGJ`_OGJ`cF`rr@Q~>
+JaVdjrr@W<JaS*WMs^QFrpKdds6fjds6fmerpBadJaS*WJaS*Wq!S.^J,~>
+Ja2Lbrr@W8Ja.gOMs:9Brp'L`s6BR`s6BUarosI`Ja.gOJa.gOq!.kZJ,~>
+J`c4Zrr@W4J`_OGMrk$>roX7\s5s=\s5s@]roO4\J`_OGJ`_OGpu_SVJ,~>
+JaVdjrr@W<JaS*W[I+"prU'^emdC&Ss6fme"muBY!!)Hc!!)Hc!!)H8!<2Ed!<2Ed!.jQhmXaei
+mf*9;~>
+Ja2Lbrr@W8Ja.gO[H[_lrTXFalK\?Ks6BUa"mQ*Q!!)<_!!)<_!!)<4!<29`!<29`!.jEdl@J5a
+lMgj7~>
+J`c4Zrr@W4J`_OG[H7JhrT41]k2u[Cs5s@]"m5jIs8V6[rrD3[rrD30s8M0\s8M0\s+0<`k(2ZY
+k5PF3~>
+JaVdjrr@W<JaS*Wg$Sh?rpKddrpKacs6fgcs6fjd!:KL[!:I#j!:Kgds6fjds6fjdJaS*WJaT&r
+rr@Q~>
+Ja2Lbrr@W8Ja.gOg$/P;rp'L`rp'I_s6BO_s6BR`!:'4W!:$`f!:'O`s6BR`s6BR`Ja.gOJa/cj
+rr@Q~>
+J`c4Zrr@W4J`_OGg#`;7roX7\roX4[s5s:[s5s=\!9WtS!9UKb!9X:\s5s=\s5s=\J`_OGJ``Kb
+rr@Q~>
+JaVdjrr@W<JaSZg!!)uc!!)uc!!)ucrrE&drrE&drrE&dr;Zici;`lGM#[VF!!%T<JaS*WT'lUY
+J,~>
+Ja2Lbrr@W8Ja/B_!!)u_!!)u_!!)u_rrE&`rrE&`rrE&`r;Zi_i;`lCM#[VB!!%T8Ja.gOT'H=U
+J,~>
+J`c4Zrr@W4J``*W!<<#[!<<#[!<<#[s8W)\s8W)\s8W)\rVll[iVro?M>mY>s8RZ4J`_OGT'$%Q
+J,~>
+JaVdjrr@W<JaW=$rrE)eJH16$irB/K!!%T<JaS*WSaQLXJ,~>
+Ja2Lbrr@W8Ja3$qrrE)aJH16$irB/G!!%T8Ja.gOSa-4TJ,~>
+J`c4Zrr@W4J`cais8W,]JcC<$j8T2Cs8RZ4J`_OGS`]qPJ,~>
+JaVdjrr@W<QgOhRrU0[cs6fO[!:GC<JH4:%!q$$gJaS*WJaT&rrr@Q~>
+Ja2Lbrr@W8Qg+PNrTaC_s6B7W!:#+8JH4:%!pTacJa.gOJa/cjrr@Q~>
+J`c4Zrr@W4Qf\;JrT=.[s5s"S!9Sk4JcF@%!p9O_J`_OGJ``Kbrr@Q~>
+JaVdjrr@W<\*a4rrpKddrpKacs6fjds6fjds6fjd!:Kdc!:GC<JH3IcJaS*WJaT#qrr@Q~>
+Ja2Lbrr@W8\*<qnrp'L`rp'I_s6BR`s6BR`s6BR`!:'L_!:#+8JH3IcJa.gOJa/`irr@Q~>
+J`c4Zrr@W4\)m\jroX7\roX4[s5s=\s5s=\s5s=\!9X7[!9Sk4JcEOcJ`_OGJ``Harr@Q~>
+JaVdjrrA&H!!)uc!!)uc!!)ucrrE&dr;ZicqZ$Waqu?`br;clcr;ZicJH16$JH5KGJaS*WJaT&r
+rr@Q~>
+Ja2LbrrA&D!!)u_!!)u_!!)u_rrE&`r;Zi_qZ$W]qu?`^r;cl_r;Zi_JH16$JH5KGJa.gOJa/cj
+rr@Q~>
+J`c4ZrrA&@!<<#[!<<#[!<<#[s8W)\rVll[qu6ZYr;QcZrVuo[rVll[JcC<$JcGQGJ`_OGJ``Kb
+rr@Q~>
+JaVdjrrCpDrrE&drrE&drr<)f!<)<j!:BjTmJuYTrU'Xcq<e4_rU($nmJu\C!:BjT!:K:S!<2Ed
+!<2Ed!<;Kd!!)Hd!<;K`!!)G<!.b-$!6kLpmXaeWm[ikH!.Y~>
+Ja2LbrrCp@rrE&`rrE&`rr<)b!<)0f!9sRLl2^)LrTX@_q<@q[rTXajl2^,7!9sRL!:&kK!<29`
+!<29`!<;?`!!)<`!<;?\!!);8!.b-$!6kLpl@J5OlCR;@!.Y~>
+J`c4ZrrCp<s8W)\s8W)\s8N,^s8D'bs5s@Dk5XTDrT4+[q;q\WrT4Lfk5XT+s5s@Ds5rJCs8M0\
+s8M0\s8V6\rrD3\s8V6XrrD24s+13$s3:Rpk(2ZGk+:`8!.Y~>
+JaVdjrrD?PJH16$JH16$m/V_*JaS*WRdU1UJ,~>
+Ja2LbrrD?LJH16$JH16$m/V_&Ja.gORd0nQJ,~>
+J`c4ZrrD?HJcC<$JcC<$mJhb"J`_OGRcaVMJ,~>
+JaVdjrrDTWJH5';!:GC<JH16$q>c*7JaS*WS*p:VJ,~>
+Ja2LbrrDTSJH5';!:#+8JH16$q>c*3Ja.gOS*L"RJ,~>
+J`c4ZrrDTOJcG-;!9Sk4JcC<$qYu-/J`_OGS*'_NJ,~>
+JaVdjrrDf]JH16$JH16$h>i,pJaS*WSF6CWJ,~>
+Ja2LbrrDfYJH16$JH16$h>i,lJa.gOSEg+SJ,~>
+J`c4ZrrDfUJcC<$JcC<$hZ&/hJ`_OGSEBhOJ,~>
+JaVdjrrE&dJH16$JH2/>!:Kdc!:Kgd!:KU^!:K[`!:JJ>JaS*WJaT)srr@Q~>
+Ja2LbrrE&`JH16$JH2/>!:'L_!:'O`!:'=Z!:'C\!:&2:Ja.gOJa/fkrr@Q~>
+J`c4ZrrE&\JcC<$JcD5>!9X7[!9X:\!9X(V!9X.X!9Vr6J`_OGJ``Ncrr@Q~>
+JaVdjJH16$JH3mo!:JqK!q$'Vqu?`brW)udq>_#n!:BjT!:BjT!!)HT!:Kgd!:K[`!:Kdcs6]sh
+mJu\S!<2Bf!:K:M!!)Hc!!)Hc!!)Hd!<2Ed!4qRu!<)<c!<)<c!.jQhmXafqmf*9;~>
+Ja2LbJH16$JH3mo!:&YG!pTdNqu?`^rW)u`q>_#j!9sRL!9sRL!!)<L!:'O`!:'C\!:'L_s69[d
+l2^,K!<26b!:&kE!!)<_!!)<_!!)<`!<29`!4qFq!<)0_!<)0_!.jEdl@J6ilMgj7~>
+J`c4ZJcC<$JcEso!9WDC!p9OFr;QcZrr<#\qYq&fs5s@Ds5s@Ds8V6Ds5s=\!9X.X!9X7[s5jF`
+k5XTCs8M-^s5rJ=rrD3[rrD3[rrD3\s8M0\s17=ms8D'[s8D'[s+0<`k(2[ak5PF3~>
+JaVmmJH16$nGiRWJH4s8!:Kabs6fdbs6]pgmK!7SrW!#f!:Kgd!q$'Vqu?`bqu?lf!:BjTrW!&g
+!:BjcmK3IW!:Kgds6^0nmJu\C!:BjT!<2Ec!!)HO!!)Ha!!_ikmJm7T!<)?a!64I,!!)HL!!)Hc
+!!)Hc!!)Hd!<;Kd!<;Kd!<;Ke!<2Ed!<2Ed!<2Bd!.jQhm[<MC!.Y~>
+Ja2UeJH16$nGiRSJH4s8!:'I^s6BL^s69Xcl2^hKrW!#b!:'O`!pTdNqu?`^qu?lb!9sRLrW!&c
+!9sR_l2q%O!:'O`s69mjl2^,7!9sRL!<29_!!)<K!!)<]!!_]gl2UhL!<)3]!64=(!!)<H!!)<_
+!!)<_!!)<`!<;?`!<;?`!<;?a!<29`!<29`!<26`!.jEdlC$r;!.Y~>
+J`c=]JcC<$nc&UOJcG$8!9X4Zs5s7Zs5jC_k5YGCrr3&^s5s=\!p9OFr;QcZr;Qo^s5s@Drr3)_
+s5s@[jobYGs5s=\s5jXfk5XT+s5s@Ds8M0[rrD3GrrD3Yrs%Wck5YJDs8D*Ys2O4$rrD3DrrD3[
+rrD3[rrD3\s8V6\s8V6\s8V6]s8M0\s8M0\s8M-\s+0<`k*bB3!.Y~>
+JaVjlJH16$fDts>rW)ud#6=/lmJuYTmbRpDmdL2[mJm7T!:Kgd!:Kgd#jq]\!:K7TmdC#R"7?-h
+mf!1jmJuYTmd:)SmJm7dmK*CV!<2Bd!<2Bf!:BjdmK*CVmf!1emK!7S!!)uch>mQCo`4[Qrr<&e
+rW)ud!<DQe!;c-_!<2E[!!2KfrpKI[cgJIH!:Kgds6fmeJaS*WR-stSJ,~>
+Ja2RdJH16$fDts:rW)u`#6=#hl2^)LlJ;L@lL4cWl2UhL!:'O`!:'O`#jMET!:&hLlK\<J"6ojd
+lM^bfl2^)LlKSBKl2Uh`l2gtN!<26`!<26b!9sR`l2gtNlM^bal2^hK!!)u_h>mQ?o`4[Mrr<&a
+rW)u`!<DEa!;c![!<29W!!2?brp'1Wcg&1D!:'O`s6BUaJa.gOR-O\OJ,~>
+J`c:\JcC<$f`2!6rr<#\#QNrdk5XTDk2-+<k4&BSk5YJDs5s=\!9X:\#j20Ls5rJDk2uXB"6TX`
+k5PAbk5XTDk2u^CjoGG\joYSFs8M-\s8M-^s5s@\joYSFk5PA]k5YGC!<<#[hZ*T;p&F^Is8N)]
+rr<#\!WV<]s8(mWs8M0SrrM9^roWqScfVq@!9X:\s5s@]J`_OGR-+DKJ,~>
+JaVgkJH16$o`,']!:Kgd!:Kgds6faas6fme!q$'VrVurdrW!)h!:BgerpK^bs6fmes6f^`"7?-h
+meZtomJm7TmJm7T!:BjT!!)H_!!)Hd!!)Hd!!)Hd!!2KfrpBdemf!4dmJm7dmf*:emf!4dmf*:e
+mK3IW!!)uc!!)uc!!)uc!!)uc!!)uc#QX8mmd:)C!!*#dli7%RquH`a!<DQe!;l0a!<2Be!:Kgd
+o'QPZmdC&SrU'dgmJuYTr9a[fmJuYTrpBgfmdC&Ss6]jerpBadqsOF`i9gXHmd?tQ!q$$gJaS*W
+QgXkRJ,~>
+Ja2OcJH16$o`,'Y!:'O`!:'O`s6BI]s6BUa!pTdNrVur`rW!)d!9sOarp'F^s6BUas6BF\"6ojd
+lMCPkl2UhLl2UhL!9sRL!!)<[!!)<`!!)<`!!)<`!!2?brosLalM^e`l2Uh`lMgkalM^e`lMgka
+l2q%O!!)u_!!)u_!!)u_!!)u_!!)u_#QX,ilKSB7!!*#`li7%NquH`]!<DEa!;l$]!<26a!:'O`
+o'-8VlK\?KrTXLcl2^)Lr9=Cbl2^)LrosOblK\?Ks69RarosI`qs+.\i9C@DlKY8I!pTacJa.gO
+Qg4SNJ,~>
+J`c7[JcC<$p&>*Us5s=\!9X:\s5s4Ys5s@]!p9OFrr2u\rr3,`s5s@]roX1Zs5s@]s5s1X"6TX`
+k55/gk5YJDk5YJDs5s@Ds8V6WrrD3\rrD3\rrD3\rrM9^roO7]k5PD\joGG\k5YJ]k5PD\k5YJ]
+jobYGs8W&[!<<#[!<<#[!<<#[!<<#[#lj&ek2u^+s8W)\m/I(Jr;ZcY!WV<]s81pYs8M-]s5s=\
+o&^#Rk2u[CrT47_k5XTDr8n.^k5XTDroO:^k2u[Cs5j=]roO4\qr[nXi8t+@k2rTA!p9O_J`_OG
+Qfe;JJ,~>
+JaVdjJH1l6rpKdds6fmes6fmes6^$jmd:&Tmf!1lmJm7TmJm7Tmed%bme?e^mf*:emf*7lmJu\C
+!:K7TqsOIarpKdd!:KL[!:Kab"RZ6imdC&SrpKdd"muBYmJu\N!!)HR!<;He!<2Ed!<2Ed!<2Bd
+!:fIW!<)<c!<)?c!<2Bd!<2Bd!;c-_!!)Hb!<2Bn!:BjT!:BjT!:Bjdmf!1emK!4R!!)ucrr</h
+!:BjZme-Y\mK3IWmK!7S$39M^!:K7TmJu\CrW!)h!:BgerpBadrU'dgmJuYTna;P?JaS*WQL=bQ
+J,~>
+Ja2LbJH1l6rp'L`s6BUas6BUas69aflKS?LlM^bhl2UhLl2UhLlMLV^lM(AZlMgkalMghhl2^,7
+!:&hLqs+1]rp'L`!:'4W!:'I^"R5selK\?Krp'L`"mQ*Ql2^,F!!)<N!<;<a!<29`!<29`!<26`
+!:f=S!<)0_!<)3_!<26`!<26`!;c![!!)<^!<26j!9sRL!9sRL!9sR`lM^bal2^eJ!!)u_rr</d
+!9sRVlLk5Xl2q%Ol2^hK$39AV!:&hLl2^,7rW!)d!9sOarosI`rTXLcl2^)Ln`l8;Ja.gOQKnJM
+J,~>
+J`c4ZJcCr6roX7\s5s@]s5s@]s5jLbk2u^Dk5PAdk5YJDk5YJDk5>5Zk4nuVk5YJ]k5YGdk5XT+
+s5rJDqr[qYroX7\!9WtS!9X4Z"Qoaak2u[CroX7\"m5jIk5XT>rrD3Js8V3]s8M0\s8M0\s8M-\
+s7,4Os8D'[s8D*[s8M-\s8M-\s8(mWrrD3Zs8M-fs5s@Ds5s@Ds5s@\k5PA]k5YDB!<<#[s8N2`
+s5s@Rk4\iTjobYGk5YGC$NK8Ns5rJDk5XT+rr3,`s5s@]roO4\rT47_k5XTDn`H#7J`_OGQKJ2I
+J,~>
+JaVdjJH3"V!:KX_"RZ9XmK!7SrVurdq#CWe!:BjTmK!4R"9@iimdC&S&afYe!!)EemJm7T!!)Ee
+md:)Rmf*:emK`g\mJu\C!:BjdmKN[Z!:BjT!<2Bd!;5d[!!;Qgmed%bme-V]mK!(N!<DQ\!<2Be
+!:KgdqX4=_g[5%ArpBadrpKddrU'XcrU'Xcr9j.V#4;HkmJm7ToDnaU"p")Z!:K7Tna?AVs6]je
+rpBadrpBgfmK!7S!!*#d#lsD]!:K7Tmd:)MmKN[ZmJu\C!;,]A!.jQhmZm5?!.Y~>
+Ja2LbJH3"V!:'@["R6!Pl2^hKrVur`q#CWa!9sRLl2^eJ"9@]elK\?K&aBA]!!)9al2UhL!!)9a
+lKSBJlMgkal3ICTl2^,7!9sR`l377R!9sRL!<26`!;5XW!!;EclMLV^lLk2Yl2^YF!<DEX!<26a
+!:'O`qWe%[gZeb=rosI`rp'L`rTX@_rTX@_r9EkR#3l0gl2UhLoDnaQ"p!rR!:&hLn`p)Rs69Ra
+rosI`rosObl2^hK!!*#`#ls8U!:&hLlKSBEl377Rl2^,7!;,Q=!.jEdlBUZ7!.Y~>
+J`c4ZJcE(V!9X+W"QoaHk5YGCrr2u\q>UZ]s5s@Dk5YDB"TRWak2u[C&a',Us8V6]k5YJDs8V6]
+k2u^Bk5YJ]jp;"Lk5XT+s5s@\jp(kJs5s@Ds8M-\s7POSrrV?_k5>5Zk4\fUk5Y8>!WV<Ts8M-]
+s5s=\qW@eWgZAM9roO4\roX7\rT4+[rT4+[r9!VN#3Psck5YJDo`+dM#63iJs5rJDn`KiNs5j=]
+roO4\roO:^k5YGC!<<&\$30/Ms5rJDk2u^=jp(kJk5XT+s7GH9s+0<`k*>*/!.Y~>
+JaVmmJH5QI!:Kdc!q$'Vqu?fd!:Kgd!:Kgd$17cnmd:)C!:K:S!!)H^!<2Bk!:BjT!:BjTpAb3]
+rW)udo)SgY!<DQe!!)Hc!!)Hc!<)<c!8[&N!:K7T!:K7T!:K7TrU0[crpKddrU'Xcp[8%]s6^*l
+mJuYTmJm7dmed%bmf!4^mJm7cmK!=Ur;cib!!)uc"9@iimK!1Q"9@iimJu#0"p")Z!:K7Tna6P\
+md:)Cmem.cmJm7dmL'$_!:BjT!:BjT!:KgdrpBadqsFFarpBadrpBadoBlb^mJuYT!<)<c!<)<c
+!<)<c!<)?c!<2Bd!<2Bd!;c*`!<2Bd!<2Bd!;Go>!.jQhm[<MC!.Y~>
+Ja2UeJH5QI!:'L_!pTdNqu?f`!:'O`!:'O`$0hKjlKSB7!:&kK!!)<Z!<26g!9sRL!9sRLpAb3Y
+rW)u`o)SgU!<DEa!!)<_!!)<_!<)0_!8ZoJ!:&hL!:&hL!:&hLrTaC_rp'L`rTX@_pZhbYs69gh
+l2^)Ll2Uh`lMLV^lM^eZl2Uh_l2^nMr;ci^!!)u_"9@]el2^bI"9@]el2]T("p!rR!:&hLn`g8X
+lKSB7lMU__l2Uh`l3dUW!9sRL!9sRL!:'O`rosI`qs".]rosI`rosI`oBHJZl2^)L!<)0_!<)0_
+!<)0_!<)3_!<26`!<26`!;bs\!<26`!<26`!;Gc:!.jEdlC$r;!.Y~>
+J`c=]JcGWI!9X7[!p9OFr;Qi\s5s=\!9X:\$0M9fk2u^+s5rJCrrD3Vs8M-cs5s@Ds5s@Dp\t6U
+rr<#\oDejQ!WV<]rrD3[rrD3[s8D'[s4ufFs5rJDs5rJDs5rJDrT=.[roX7\rT4+[pZDMUs5jRd
+k5XTDk5YJ\k5>5Zk5PDVjoGG[joPMErVulZ!<<#["TRWak5YAA"TRWak5X2u#63iJs5rJDn`C#T
+k2u^+k5G>[joGG\jpV4Os5s@Ds5s@Ds5s=\roO4\qrRnYroO4\roO4\oB$5Vk5XTDs8D'[s8D'[
+s8D'[s8D*[s8M-\s8M-\s8(jXs8M-\s8M-\s7bZ6s+0<`k*bB3!.Y~>
+NpZlIr9aObr9aObr9aObn*\d-s6f^`!:KX_$gmupmJu\C!:BgemeHh_mf!1imJuYT!:KX_!:Kdc
+#jqZmmd:&TmdC&S!:Kdc!:Kgd!:Kgdr9jRbs6faa"RZ9X!:KabrpBadrpC'mmd:)C!:BgemeHk^
+mK!=UnGiRWo`4sY!!)uc!!*#d#lsD]!!)EemJm7?mL90amJuYTmd:)C!:K7Tl0\NOrU0[cs6fgc
+rpBdeme-YTmK<OX!:Bj`mKN[ZmJu\C!<2Bj!:K7Tmd:)0mK<OX!:BjWmJm7dmL'$_!:BjT!:BjT
+!:Kgds6fjds6]shmJu\S!<2Bd!<)<c!<)<c!<)<c!;5aa!:K7Tmd:)CmJm7cmJm7cmJm7bmJm7c
+mJm7cmJm7]mYq3tmXaenmf*9;~>
+Np6TEr9=7^r9=7^r9=7^n*8L)s6BF\!:'@[$gI]ll2^,7!9sOalM1D[lM^bel2^)L!:'@[!:'L_
+#jMBilKS?LlK\?K!:'L_!:'O`!:'O`r9F:^s6BI]"R6!P!:'I^rosI`rosdilKSB7!9sOalM1GZ
+l2^nMnGiRSo`4sU!!)u_!!*#`#ls8U!!)9al2Uh;l4!aYl2^)LlKSB7!:&hLl086KrTaC_s6BO_
+rosLalLk5Pl3%+P!9sR\l377Rl2^,7!<26f!:&hLlKSB(l3%+P!9sRSl2Uh`l3dUW!9sRL!9sRL
+!:'O`s6BR`s69[dl2^,K!<26`!<)0_!<)0_!<)0_!;5U]!:&hLlKSB;l2Uh_l2Uh_l2Uh^l2Uh_
+l2Uh_l2UhYlAYdpl@J5flMgj7~>
+Nog<Ar8mtZr8mtZr8mtZn)aTMf`2!6qYpQXq>Uibs8V6]k2u^+s8V6WrrD3\rrqQbk5YJDq>UHW
+rVm/cs8V6Ds8V6Drr2u\rVll[rr2u\rr;oYs8W,]qu6f]s5s@Dr;ZcY!<<&\$30/Ms5s@Ds8V6W
+s8M-]s5rkO!9WtSrT4+[rT4+[roOOek2u^Dk5XTDs4QNDs5rJDk5XT+s5s@Dk5X].!<<#[s8W,]
+rVulZ!WV<Us7bXYs5s@Ds8(j^s5rJDk2u^Cjp(kJk5XT+s4cZ=s5s@Ds7,4Os8M-gs5s@Ds5s@D
+s5s@Drr<#\rr<#\"97N`k5PD[joGG[joGG[joGG[joGGSjp(kJk5XT+s6f"Ls8D'[s8D'[s8;!Z
+s8D'[s8D'[s7bZ8s+0<`k*Y<2!.Y~>
+O7*#JrU'^emK!7S!W_WgrpBgfmJubEj8f/H!W_ZVrW!2k!!)EemJu\S!!)H_!!_ikmd:&TmeQn`
+mf!1dmem+cmem.cmf!1fmJu\S!!)H`!!)Hc!!)Hd!!)Hd!!)Hc!<;Ke!<)<c!<)<g!:BjTmdpJ[
+mK!4R!!)oap&OmUqZ-Zarr<&erVurdqu?ie!:Bj[mL'$_mJuYTmJu\C!!'4j!!*#d!!*#d#6=/l
+md:)Cmem.XmKN[ZmJu\C!;u6b!<2Bd!<2Bf!:BjdmJm7dmJm7Amf!4VmJm7cmf*:emem.bmJm7\
+mKN[ZmJu\C!9EPJ!<2Bd!<2Bd!6OYl!!2KfJaS*WQL=bQJ,~>
+O6Z`FrTXFal2^hK!W_KcrosObl2^>=j8f/D!W_NNrW!2g!!)9al2^,K!!)<[!!_]glKS?LlM:J\
+lM^b`lMU\_lMU__lM^bbl2^,K!!)<\!!)<_!!)<`!!)<`!!)<_!<;?a!<)0_!<)0c!9sRLlLY&W
+l2^eJ!!)o]p&OmQqZ-Z]rr<&arVur`qu?ia!9sRWl3dUWl2^)Ll2^,7!!'4f!!*#`!!*#`#6=#h
+lKSB7lMU_Tl377Rl2^,7!;u*^!<26`!<26b!9sR`l2Uh`l2Uh=lM^eRl2Uh_lMgkalMU_^l2UhX
+l377Rl2^,7!9EDF!<26`!<26`!6OMh!!2?bJa.gOQKnJMJ,~>
+O66HBrT4.]joGDC!W_?_roO7^joFo5jT#2@!rqEFrr35cs8V6]k5XTCrrD3Wrs%Wck2u^Dk5,)X
+k5PA\k5G;[k5G>[k5PA^k5XTCrrD3XrrD3[rrD3\rrD3\rrD3[s8V6]s8D'[s8D'_s5s@Dk4JZS
+k5YDB!<;rYpAapMqu?]Ys8N)]rr2u\r;Ql]s5s@SjpV4Ok5XTDk5XT+s8T:b!<<&\!<<&\#QNrd
+k2u^+k5G>Pjp(kJk5XT+s8;!Zs8M-\s8M-^s5s@\joGG\joGG9k5PDNjoGG[k5YJ]k5G>ZjoGGT
+jp(kJk5XT+s5`;Bs8M-\s8M-\s2jDdrrM9^J`_OGQKJ2IJ,~>
+OR</MmK!7S!!*#d!W_WgrpBgfmK!7S!!)TXkQ(GH!W_ZVr;clcrr<&enGij_!:K7T!:BjTr;Zic
+r;ZicrW)udo`,![p&OaQ"9@iimJuqJ!!*#d!!*#d!<DQe!:9.Q!;5a^!:BjTrW)udrrE)e!<DQb
+!!M]imd:)ImJm7cmJm7cmJm6lmJm7cmJm7cmJm7dmK*CVmf!1emJutK!!*#d!!*#d!!*#d!!)uc
+!!)uc!!)uc!!)uc!!(sFp&OUM!!*#d!!*#d!!)ZZ!!*#d!!*#d!!)0L!!)uc!!)uc!!(./OoPON
+!.jQhm[*AA!.Y~>
+OQllIl2^hK!!*#`!W_KcrosObl2^hK!!)TTkQ(GD!W_NNr;cl_rr<&anGij[!:&hL!9sRLr;Zi_
+r;Zi_rW)u`o`,!Wp&OaM"9@]el2^MB!!*#`!!*#`!<DEa!:9"M!;5UZ!9sRLrW)u`rrE)a!<DE^
+!!MQelKSBAl2Uh_l2Uh_l2Ughl2Uh_l2Uh_l2Uh`l2gtNlM^bal2^PC!!*#`!!*#`!!*#`!!)u_
+!!)u_!!)u_!!)u_!!(sBp&OUI!!*#`!!*#`!!)ZV!!*#`!!*#`!!)0H!!)u_!!)u_!!(.+OoPOJ
+!.jEdlBgf9!.Y~>
+OQHTEjoGDC!!*#\!W_?_roO7^joGDC!!)TPkl:J@!rqEFrVuo[s8N)]nc&mWs5rJDs5s@DrVll[
+rVll[rr<#\p&>$SpAadI"TRWak5Y,:!<<&\!<<&\!WV<]s6SnIs7PLVs5s@Drr<#\s8W,]!WV<Z
+rrhKak2u^9joGG[joGG[joGFdjoGG[joGG[joGG\joYSFk5PA]k5Y/;!<<&\!<<&\!<<&\!<<#[
+!<<#[!<<#[!<<#[!<;!>pAaXE!<<&\!<<&\!<;]R!<<&\!<<&\!<;3D!<<#[!<<#[!<:1'P5bRF
+s+0<`k*P61!.Y~>
+NpZlIrpBadrpBgfmK!7S!W_WgrpBadoBu&In*U/UrpBadrpKdd#4;KZ!:BjTqZ$WarW!5l!:K7T
+mJuYTqsFFarU'XcrU'^emdC&Sq!S+]n*^/Tp$Me[rU'XcrU'dgmJuYTmd:2XmJuYTp[/.amJm7T
+qZ$]c!:Kgds6^!imd:&TrpBadOmW2LrU'pkmJu\C!:K7Tq!J+^rU'XcrU'Xc`U<^*mHsrSrU'Xc
+rU'Xcp?hn\rU'XcrU'XcV='EF!U]r=mXaenmf*9;~>
+Np6TErosI`rosObl2^hK!W_KcrosI`oBPcEn*0lQrosI`rp'L`#3l3R!9sRLqZ$W]rW!5h!:&hL
+l2^)Lqs".]rTX@_rTXFalK\?Kq!.hYn*9lPp$)MWrTX@_rTXLcl2^)LmcjoTl2^)LpZ_k]l2UhL
+qZ$]_!:'O`s69^elKS?LrosI`Om2oHrTXXgl2^,7!:&hLq!%hZrTX@_rTX@_`TmF&mHOZOrTX@_
+rTX@_p?DVXrTX@_rTX@_V<X-B!U9Z9l@J5flMgj7~>
+Nog<AroO1\roO7^joGDC!W_?_roO1\oB,NAn)aWMroO4\roX7\#3PsJs5s@Dqu6ZYrr38ds5rJD
+k5XTDqrRnYrT4+[rT41]k2u[Cpu_SUn)jWLp#Z8SrT4+[rT47_k5XTDmcFZPk5XTDpZ;VYk5YJD
+qu6`[s5s=\s5jIak2u^DroO4\OlcZDrT4Cck5XT+s5rJDpuVSVrT4+[rT4+[`TI1"mH+EKrT4+[
+rT4+[p>uATrT4+[rT4+[V<3m>!TsE5k(2Z^k5PF3~>
+NpZlIrpBadrpBgfmK!7S!W_WgrpBado^;;N!U]sTmJm7cmJm7dmeZtameHhgmJuYT!:BjTmf!4S
+mK<OX!:Bj`mK<OX!:BjZme-YMmKN[ZmJu\C!:]C\!:K7Tmd:)NmKrs^!:BjT!:BjT!;u6f!:K7T
+!<2Bd!<)<c!0$=G!<2Bd!<2Bd!42(r!:BjT!7pQ<!<)<c!<)<c!3,Co!!)H@!!2KfJaS*WRI:(T
+J,~>
+Np6TErosI`rosObl2^hK!W_KcrosI`o]l#J!U9[Pl2Uh_l2Uh`lMCP]lM1Dcl2^)L!9sRLlM^eO
+l3%+P!9sR\l3%+P!9sRVlLk5Il377Rl2^,7!:]7X!:&hLlKSBFl3[OV!9sRL!9sRL!;u*b!:&hL
+!<26`!<)0_!0$1C!<26`!<26`!41qn!9sRL!7pE8!<)0_!<)0_!3,7k!!)<<!!2?bJa.gORHjeP
+J,~>
+Nog<AroO1\roO7^joGDC!W_?_roO1\o]H,P!!)fo!TsFLjoGG[joGG\k55/Yk5##_k5XTDs5s@D
+k5PDKjok_Hs5s@Xjok_Hs5s@Rk4\iEjp(kJk5XT+s7#.Ts5rJDk2u^>jpM.Ns5s@Ds5s@Ds8;!^
+s5rJDs8M-\s8D'[s,?(?s8M-\s8M-\s0Lhjs5s@Ds46<4s8D'[s8D'[s/G.grrD38rrM9^J`_OG
+RHFMLJ,~>
+NpZlIrpBadrpBgfmK!7S!W_WgrpBadna?2Q"muBY!:BjMme-V`mJu\C!;l3X!;#U_!:K7Tmd:)Q
+mKN[ZmJu\C!:fLV!:B1S!<2Bd!<2Bd!:oOX!<2Bd!<2Bd!;l0m!:BjTmJuYTmd:)C!<2Bd!<)<c
+!<)<c!/^+D!<)<c!<)<c!4D5!!:K7Tmd:)+mJm7dmJm7dmJm6_m`YY3mJunI!!*#drrE&drrE&d
+rrE&drVurdrVurdr;clcrVurdr;clcrr@W<JaSonrr@Q~>
+Np6TErosI`rosObl2^hK!W_KcrosI`n`ooM"mQ*Q!9sRIlLk2\l2^,7!;l'T!;#I[!:&hLlKSBI
+l377Rl2^,7!:f@R!:B%O!<26`!<26`!:oCT!<26`!<26`!;l$i!9sRLl2^)LlKSB7!<26`!<)0_
+!<)0_!/]t@!<)0_!<)0_!4D(r!:&hLlKSB#l2Uh`l2Uh`l2Ug[lHB5/l2^JA!!*#`rrE&`rrE&`
+rrE&`rVur`rVur`r;cl_rVur`r;cl_rr@W8Ja/Wfrr@Q~>
+Nog<AroO1\roO7^joGDC!W_?_roO1\n`C8\rrE'!rrE)]s5s@Ds6&P<rrhKak2u^@k4\iHjp(kJ
+k5XT+s8;!`s5rJDk2u^6k5PDJjoGG\joGG\joGGPjoGG\joGG\joGGYjp_:Ps5rJDk5XT+s5s@\
+joGG[joGG[joGF<joGG[joGG[joGFhjp(kJk5XT+s46<4s8M-\s8M-\s.nf$rrM9^o]?/RroX7\
+roX7\roX7\roX4[!9X:\!9X7[s5s=\!9X7[s5s@]J`_OGR-+DKJ,~>
+NpZlIrpBadrpBgfmK!7S!W_WgrpBadna?AV$gmupmJuYT!:BjT!9ESI!!2KfrpBadrpBadq!S+]
+nEp8VrpBadrpBadrpBadrpBadrpBadoBl_]mJuYTn*U/UrU'XcrU'Xco^2\ZrU'XcrU'XcJaNL<
+rU'XcrU'Xco'QJXrU'XcrU'Xc\F'=srpBadrpBadf'W_Bmd:)CmJs'Nd/_bc$io\qmJuYTmJu\C
+!!*#d!!%T<JaS]hrr@Q~>
+Np6TErosI`rosObl2^hK!W_KcrosI`n`p)R$gI]ll2^)L!9sRL!9EGE!!2?brosI`rosI`q!.hY
+nEKuRrosI`rosI`rosI`rosI`rosI`oBHGYl2^)Ln*0lQrTX@_rTX@_o]cDVrTX@_rTX@_Ja*48
+rTX@_rTX@_o'-2TrTX@_rTX@_\EX%orosI`rosI`f'3G>lKSB7l2[XFd/_b_$ioPml2^)Ll2^,7
+!!*#`!!%T8Ja/E`rr@Q~>
+Nog<AroO1\roO7^joGDC!W_?_roO1\n`C>]!<;3]s5s@Ds8V6]k5XN)rr3#]s8M-\s8M-\s7kaU
+s7#.Ns8M-\s8M-\s8M-\s8M-\s8M-\s7>@Us5s@Ds6o(Ms8D'[s8D'[s7GFRs8D'[s8D'[s+0;4
+s8D'[s8D'[s75:Ps8D'[s8D'[s1%1ks8M-\s8M-\s46<:s5rJDk2u]>k0a4[jp_:Ps5s@Ds5s@D
+k5YJ\joGF4k(2ZXk5PF3~>
+NpZlIrU'^emK!7S!W_WgrpBgfmJubErrC^>"9@iimK!4R!!)uc!!)l`"9@iimJuhG!!)uc!!)uc
+!W_WgrU'XcrU'Xcp$N"amd:)CmJqh+bjGH2rpBadrpBadnEp8VrpBadrpBad\F'=srU'XcrU'Xc
+o'QJXrU'XcrU'XcnEpDZmJuYTV!d1A`:!U)rU'XcrU'XcJaS*WO7*#JJ,~>
+Np6TErTXFal2^hK!W_KcrosObl2^>=rrC^:"9@]el2^eJ!!)u_!!)l\"9@]el2^D?!!)u_!!)u_
+!W_KcrTX@_rTX@_p$)_]lKSB7l2ZD#bj#0.rosI`rosI`nEKuRrosI`rosI`\EX%orTX@_rTX@_
+o'-2TrTX@_rTX@_nEL,Vl2^)LV!?n=`9R=%rTX@_rTX@_Ja.gOO6Z`FJ,~>
+Nog<ArT4.]joGDC!W_?_roO7^joFo5rrC^6"TRWak5YDB!<<#[!<;oX"TRWak5Y#7!<<#[!<<#[
+!rqE_rT4+[rT4+[p#ZJYk2u^+k5U"pbiSp*roO4\roO4\nE'`NroO4\roO4\\E3ekrT4+[rT4+[
+o&]rPrT4+[rT4+[nE'lRk5XTDUupY9`9.(!rT4+[rT4+[J`_OGO66HBJ,~>
+ORE#HrpBadr9aObr9aObn*^2Ug$T%Emd:)CmJuhG"p")Z!:K7TdI$u7rpBadrpBadJaUbM"p")Z
+!:K7Tmd:8Zmd:)CmJrdF!!*#d!!*#d!!)HTrW&b^o)Q2dp&K[3JaSB_rr@Q~>
+OQu`DrosI`r9=7^r9=7^n*9oQg$/bAlKSB7l2^D?"p!rR!:&hLdHU]3rosI`rosI`Ja1JE"p!rR
+!:&hLmcjuVlKSB7l2[@>!!*#`!!*#`!!)HPrW&bZo)Q2`p&K[/Ja/*Wrr@Q~>
+OQQH@roO1\r8mtZr8mtZn)jWMg#`M=k2u^+k5Y#7#63iJs5rJDdH1H/roO4\roO4\J`b2=#63iJ
+s5rJDmcF`Rk2u^+k5Ut6!<<&\!<<&\!<;KLrr8eVoDc5\pA]^+J`_gOrr@Q~>
+JaVdjrrCd@!!*#d!!*#d!!)ZZ!!*#d!!*#d!!(L9!!)uc!!)uc!!(O:!!)uc!!)uc!!)WY!!)uc
+!!)uc!!&SX"9@iimJuVA"9@iimJr^D"p")Z!:K7Tna?)NWUB`cs6fjds6fjdYO;AiJaS*WL$ns@
+J,~>
+Ja2LbrrCd<!!*#`!!*#`!!)ZV!!*#`!!*#`!!(L5!!)u_!!)u_!!(O6!!)u_!!)u_!!)WU!!)u_
+!!)u_!!&ST"9@]el2^29"9@]el2[:<"p!rR!:&hLn`ofJWTsH_s6BR`s6BR`YNl)eJa.gOL$J[<
+J,~>
+J`c4ZrrCd8!<<&\!<<&\!<;]R!<<&\!<<&\!<:O1!<<#[!<<#[!<:R2!<<#[!<<#[!<;ZQ!<<#[
+!<<#[!<8VP"TRWak5Xf1"TRWak5Un4#63iJs5rJDn`KQFWTO3[s5s=\s5s=\YNGiaJ`_OGL$&C8
+J,~>
+JaVdjrrCgA!!)uc!!)uc!!)`\!!)uc!!)uc!!&MV!!*#d!!*#d!!)QW!!*#d!!*#d!!&MVrW)6O
+rW&>R"9@iimJuVArW%N;a6j'1mJuYTJaS*WL@5'AJ,~>
+Ja2LbrrCg=!!)u_!!)u_!!)`X!!)u_!!)u_!!&MR!!*#`!!*#`!!)QS!!*#`!!*#`!!&MRrW)6K
+rW&>N"9@]el2^29rW%N7a6Ed-l2^)LJa.gOL?ed=J,~>
+J`c4ZrrCg9!<<#[!<<#[!<;cT!<<#[!<<#[!<8PN!<<&\!<<&\!<;TO!<<&\!<<&\!<8PNrr;9G
+rr8AJ"TRWak5Xf1rr7Q3a6!O)k5XTDJ`_OGL?AL9J,~>
+JaVdjrr@W<e*[D?md:)CmJu_D"p")Z!:K7TT'l:Po'Z2OS*p7Um-XuVmJuYTJaUYJ"p")Z!:K7T
+JaS*WL[P0BJ,~>
+Ja2Lbrr@W8e*7,;lKSB7l2^;<"p!rR!:&hLT'H"Lo'5oKS*KtQm-4]Rl2^)LJa1AB"p!rR!:&hL
+Ja.gOL[+m>J,~>
+J`c4Zrr@W4e)gl7k2u^+k5Xo4#63iJs5rJDT'#bHo&fZGS*'_Mm,eHNk5XTDJ`b):#63iJs5rJD
+J`_OGLZ\U:J,~>
+JaVdjrr@W<dd@5<mJuYTmHt)WmJuYTRI:%SlL+WOS*otMna6S]md:)CmJqh+bO,?1rpBadrpBad
+JaS*WM!k9CJ,~>
+Ja2Lbrr@W8dcpr8l2^)LmHOfSl2^)LRHjbOlK\?KS*K\In`g;YlKSB7l2ZD#bN]'-rosI`rosI`
+Ja.gOM!G!?J,~>
+J`c4Zrr@W4dcL]4k5XTDmH+QOk5XTDRHFMKlK8*GS*'GEn`C&Uk2u^+k5U"pbN8g)roO4\roO4\
+J`_OGM!"^;J,~>
+JaVdjrr@W<dI-u6lgF`PRI11XmJuYTm-XuVmJuYTR-sqRmd:&TrpBadrpBadJaUeN!!)uc!!)uc
+!!%T<JaSB_rr@Q~>
+Ja2Lbrr@W8dH^]2lg"HLRHanTl2^)Lm-4]Rl2^)LR-OYNmcjcProsI`rosI`Ja1MF!!)u_!!)u_
+!!%T8Ja/*Wrr@Q~>
+J`c4Zrr@W4dH:H.lfS3HRH=YPk5XTDm,eHNk5XTDR-+DJmcFNLroO4\roO4\J`b5>!<<#[!<<#[
+!<7W4J`_gOrr@Q~>
+JaVdjrr@W<eaE,2oBu;PT'cd_md:)CmJu\C"p")Z!:K7TcL(Z4rU'XcrU'Xcf^8kBmJuYTnEp8V
+rU'XcrU'Xcp?hn\rU'XcrU'XcJaS*WJaS*WiU6^GJ,~>
+Ja2Lbrr@W8e`ui.oBQ#LT'?L[lKSB7l2^8;"p!rR!:&hLcKYB0rTX@_rTX@_f]iS>l2^)LnEKuR
+rTX@_rTX@_p?DVXrTX@_rTX@_Ja.gOJa.gOiTgFCJ,~>
+J`c4Zrr@W4e`QT*oB,cHT&p7Wk2u^+k5Xl3#63iJs5rJDcK5-,rT4+[rT4+[f]E>:k5XTDnE'`N
+rT4+[rT4+[p>uATrT4+[rT4+[J`_OGJ`_OGiTC.?J,~>
+JaVdjrrB1h!!)uc!!)uc!!'7krW)9PrW&GU!!*#d!!*#d!!)NV!!*#d!!*#d!!(=4!!*#d!!*#d
+!!(gB!!*#drr<2i!:K7Tn*U/UrU'XcrU'Xcp[/"]rpBadrpBadJaS*WJaS*Wi9pUFJ,~>
+Ja2LbrrB1d!!)u_!!)u_!!'7grW)9LrW&GQ!!*#`!!*#`!!)NR!!*#`!!*#`!!(=0!!*#`!!*#`
+!!(g>!!*#`rr<2e!:&hLn*0lQrTX@_rTX@_pZ__YrosI`rosI`Ja.gOJa.gOi9L=BJ,~>
+J`c4ZrrB1`!<<#[!<<#[!<9:crr;<Hrr8JM!<<&\!<<&\!<;QN!<<&\!<<&\!<:@,!<<&\!<<&\
+!<:j:!<<&\s8N5as5rJDn)aWMrT4+[rT4+[pZ;JUroO4\roO4\J`_OGJ`_OGi9(%>J,~>
+JaVdjrrB.g!!*#d!!*#d!!'7k"9@iimJuYB"9@iimJrgG!!)uc!!)uc!!)TX!!)uc!!)uc!!(=4
+"p")Z!:K7Tg?o:JmJuYTmJuYTmJu_D!!*#d!!*#d!!)]["p")Z!:K7TJaS*WJaS*WhsULEJ,~>
+Ja2LbrrB.c!!*#`!!*#`!!'7g"9@]el2^5:"9@]el2[C?!!)u_!!)u_!!)TT!!)u_!!)u_!!(=0
+"p!rR!:&hLg?K"Fl2^)Ll2^)Ll2^;<!!*#`!!*#`!!)]W"p!rR!:&hLJa.gOJa.gOhs14AJ,~>
+J`c4ZrrB._!<<&\!<<&\!<9:c"TRWak5Xi2"TRWak5V"7!<<#[!<<#[!<;WP!<<#[!<<#[!<:@,
+#63iJs5rJDg?&bBk5XTDk5XTDk5Xo4!<<&\!<<&\!<;`S#63iJs5rJDJ`_OGJ`_OGhraq=J,~>
+JaVdjrrB+f"p")Z!:K7TYjM\qmd:)CmJu_D"p")Z!:K7TJaUMF"9@iimJto-"p")Z!:BgerpBad
+n*UA[md:)CmJukH"9@iimJqh+JaS*WJaVC_rr@Q~>
+Ja2LbrrB+b"p!rR!:&hLYj)DmlKSB7l2^;<"p!rR!:&hLJa15>"9@]el2]K%"p!rR!9sOarosI`
+n*1)WlKSB7l2^G@"9@]el2ZD#Ja.gOJa2+Wrr@Q~>
+J`c4ZrrB+^#63iJs5rJDYiZ/ik2u^+k5Xo4#63iJs5rJDJ`ar6"TRWak5X)r#63iJs5s@]roO4\
+n)aiSk2u^+k5Y&8"TRWak5U"pJ`_OGJ`bhOrr@Q~>
+JaVdjrrB(e"9@iimJsKZ!!*#d!!*#d!!)QW!!*#d!!*#d!!%T<`U<^*f'WY@mJuYTl0\ZSmJuYT
+na?AVJaS*WJaS*Wh<t:CJ,~>
+Ja2LbrrB(a"9@]el2\'R!!*#`!!*#`!!)QS!!*#`!!*#`!!%T8`TmF&f'3A<l2^)Ll08BOl2^)L
+n`p)RJa.gOJa.gOh<P"?J,~>
+J`c4ZrrB(]"TRWak5V[J!<<&\!<<&\!<;TO!<<&\!<<&\!<7W4`TI1"f&d,8k5XTDl/i-Kk5XTD
+n`KiNJ`_OGJ`_OGh<+_;J,~>
+JaVdjrrB%drW'1j!!)uc!!)uc!!)WY!!)uc!!)uc!!%T<b3ns'g$\h>kO/<Lo^;DQJaS*WJaS*W
+iU6^GJ,~>
+Ja2LbrrB%`rW'1f!!)u_!!)u_!!)WU!!)u_!!)u_!!%T8b3J[#g$8P:kN`$Ho]l,MJa.gOJa.gO
+iTgFCJ,~>
+J`c4ZrrB%\rr94b!<<#[!<<#[!<;ZQ!<<#[!<<#[!<7W4b3&Etg#i;6kN;dDo]GlIJ`_OGJ`_OG
+iTC.?J,~>
+JaVdjrrB1hp&LNK!!)uc!!)uc!!%T<aR9$-g$\P6n*]lLo^;\YJaS*WJaS*Wh<t:CJ,~>
+Ja2LbrrB1dp&LNG!!)u_!!)u_!!%T8aQia)g$882n*9THo]lDUJa.gOJa.gOh<P"?J,~>
+J`c4ZrrB1`pA^QC!<<#[!<<#[!<7W4aQEL%g#i#.n)j?Do]H/QJ`_OGJ`_OGh<+_;J,~>
+JaVdjrrB%drW&2N!!*#d!!*#d!!%T<aR002mJuYTf'`M;kO/<Lna6M[mJuYTJaS*WJaS*WhX:CD
+J,~>
+Ja2LbrrB%`rW&2J!!*#`!!*#`!!%T8aQ`m.l2^)Lf'<57kN`$Hn`g5Wl2^)LJa.gOJa.gOhWk+@
+J,~>
+J`c4ZrrB%\rr85F!<<&\!<<&\!<7W4aQ<X*k5XTDf&lu3kN;dDn`BuSk5XTDJ`_OGJ`_OGhWFh<
+J,~>
+JaVdjrrB(e"9@iimJrL>"p")Z!:K7TJaUVI"p")Z!:K7Tf^8kBmJuYTl0\ZSmJuYToBle_md:)C
+mJqh+JaS*WJaVF`rr@Q~>
+Ja2LbrrB(a"9@]el2[(6"p!rR!:&hLJa1>A"p!rR!:&hLf]iS>l2^)Ll08BOl2^)LoBHM[lKSB7
+l2ZD#Ja.gOJa2.Xrr@Q~>
+J`c4ZrrB(]"TRWak5U\.#63iJs5rJDJ`b&9#63iJs5rJDf]E>:k5XTDl/i-Kk5XTDoB$8Wk2u^+
+k5U"pJ`_OGJ`bkPrr@Q~>
+JaVdjrrB+f"p")Z!:K7TPjSYSmJuYTJaUVI!!*#d!!*#d!!(a@"p")Z!:K7Tlg=rWmd:)CmJuqJ
+!!*#d!!*#d!!%T<JaS*WJaVIarr@Q~>
+Ja2LbrrB+b"p!rR!:&hLPj/AOl2^)LJa1>A!!*#`!!*#`!!(a<"p!rR!:&hLlfnZSlKSB7l2^MB
+!!*#`!!*#`!!%T8Ja.gOJa21Yrr@Q~>
+J`c4ZrrB+^#63iJs5rJDPi`,Kk5XTDJ`b&9!<<&\!<<&\!<:d8#63iJs5rJDlfJEOk2u^+k5Y,:
+!<<&\!<<&\!<7W4J`_OGJ`bnQrr@Q~>
+JaVjl"T[rj!:BjcmJm7%mJm7cmJm7cmK*CV!<2Bd!<2Bd!0lpN!.jRZmJm7cmJm7cmJm7BmJm7d
+mJm7dmJm7SmJm7dmJm7dmJm7]mJm7cmJm7cmJm6<mXaeWmXafbmf*9;~>
+Ja2Rd"T[ff!9sR_l2Uh!l2Uh_l2Uh_l2gtN!<26`!<26`!0ldJ!.jFVl2Uh_l2Uh_l2Uh>l2Uh`
+l2Uh`l2UhOl2Uh`l2Uh`l2UhYl2Uh_l2Uh_l2Ug8l@J5Ol@J6ZlMgj7~>
+J`c:\"om]b!9X=[joGFrjoGG[joGG[joYSFs8M-\s8M-\s-2[Fs+0=RjoGG[joGG[joGG:joGG\
+joGG\joGGKjoGG\joGG\joGGUjoGG[joGG[joGF4k(2ZGk(2[Rk5PF3~>
+JaVgkrVuue!<2Bd!57e#!<2Bd!<2Bf!:BjcmJm7cmJm6Tme-Y\mJm7cmJm7cmJm6<mcj->!<)<c
+!<)<c!6sp3!<)<c!<)<c!:T=U!<)<c!<)<c!.jQhmXaeWm`k2"!.Y~>
+Ja2OcrVuua!<26`!57Xt!<26`!<26b!9sR_l2Uh_l2UgPlLk5Xl2Uh_l2Uh_l2Ug8lKRR6!<)0_
+!<)0_!6sd/!<)0_!<)0_!:T1Q!<)0_!<)0_!.jEdl@J5OlHSVo!.Y~>
+J`c7["TJH%k5YGC!<9ap!<<&\!<<&\!rqE_rT4+[rT4+[RHF5Cs5j=]rT4+[rT4+[J`c4Z!<<#[
+!<<#[!<:=+!<<#[!<<#[!<;NM!<<#[!<<#[!<7W4J`_OGJ`b5>rr@Q~>
+JaVdjrr<2i!:K7T_sRL)rU'slmd:)C!!)HT!/U(B!;c*`!<2Bd!<2Bd!.jS$mJm7dmJm7dmJm6<
+mXaeWmXaeWmdTZE!.Y~>
+Ja2Lbrr<2e!:&hL_s.4%rTX[hlKSB7!!)<L!/Tq>!;bs\!<26`!<26`!.jFul2Uh`l2Uh`l2Ug8
+l@J5Ol@J5OlL=*=!.Y~>
+J`c4Z#6+\cs5rJD_r^t!rT4Fdk2u^+s8V6Ds+oh:s8(jXs8M-\s8M-\s+0=qjoGG\joGG\joGF4
+k(2ZGk(2ZGk4%O5!.Y~>
+JaVdjrr</h!:BjKmJm7cmJm7cmJm7NmJm7dmJm7dmK<OX!:BiCmK<OX!:Bj`mKN[ZmJu\C!.jS"
+mKN[ZmJu\C!.jQhmXaeWmXafpmf*9;~>
+Ja2Lbrr</d!9sRGl2Uh_l2Uh_l2UhJl2Uh`l2Uh`l3%+P!9sQ?l3%+P!9sR\l377Rl2^,7!.jFs
+l377Rl2^,7!.jEdl@J5Ol@J6hlMgj7~>
+J`c4Z"T\Sbs5s@CjoGG[joGG[joGGFjoGG\joGG\jok_Hs5s?;jok_Hs5s@Xjp(kJk5XT+s+0=o
+jp(kJk5XT+s+0<`k(2ZGk(2[`k5PF3~>
+JaVdjquGdF!!*#d!!*#d!!)0L#6=2[!:K7Tmf!3ImJm7cmK`g\!:K7Tmd:)OmK<OX!:Bi<mc3^<
+!:BjT!.jQhmXaeWmXafomf*9;~>
+Ja2LbquGdB!!*#`!!*#`!!)0H#6=&S!:&hLlM^dEl2Uh_l3ICT!:&hLlKSBGl3%+P!9sQ8lJq.4
+!9sRL!.jEdl@J5Ol@J6glMgj7~>
+J`c4ZrrE&uj5p@AroO4\roO4\k2lpKk2u^+k5XTCs,Z:Bs8D'cs5s@Dk5XT+s8(j\s5s@Ds+0=m
+jok_Hs5s?4k(2ZGk(2ZGk3hC3!.Y~>
+JaVjlp&O7C"p")Z!:K7TjR*$Kme$RCmJm7dmf*:emf*:dmJm7`mf!3FmJm7cmJm7cmJm7Gmf!3;
+mXaeWmXaeWmd9HB!.Y~>
+Ja2Rdp&O7?"p!rR!:&hLjQZaGlLb.?l2Uh`lMgkalMgk`l2Uh\lM^dBl2Uh_l2Uh_l2UhClM^d7
+l@J5Ol@J5OlL!m:!.Y~>
+J`c:\pAa:;#63iJs5rJDjQ6LCk4Sb;joGG\k5YJ]k5YJ\joGGXk5PC>joGG[joGG[joGG?k5PC3
+k(2ZGk(2ZGk3_=2!.Y~>
+JaVdjquG^D"9@iimJu87rW)rcrW%rG#QX;\!:K7TmK!4R!W_ZVp&L0A!!*#d!!*#d!!)*Jp&K[3
+JaS*WJaS*Wna?DWJ,~>
+Ja2LbquG^@"9@]el2]i/rW)r_rW%rC#QX/T!:&hLl2^eJ!W_NNp&L0=!!*#`!!*#`!!)*Fp&K[/
+Ja.gOJa.gOn`p,SJ,~>
+J`c4ZrrE&uiT::Ck5XTDio^7?roX4[NTLNHk2u^+k5XTDrT41]k2uC;O6-HBroO4\roO4\jQ?19
+J`_OGJ`_OGJ`cLbrr@Q~>
+JaVdjrr</h!:BjGmf!4Jme$P\mJs'N!!)uc!!)uc!!)]["9@iimJukHrW%iD"p")Z!:K7ThsUID
+JaS*WJaS*WJaVpnrr@Q~>
+Ja2Lbrr</d!9sRClM^eFlLb,Xl2[XF!!)u_!!)u_!!)]W"9@]el2^G@rW%i@"p!rR!:&hLhs11@
+Ja.gOJa.gOJa2Xfrr@Q~>
+J`c4Z"T\Sbs5s@?k5PDBk4S`Tk5V7>!<<#[!<<#[!<;`S"TRWak5Y&8rr7l<#63iJs5rJDhraq<
+J`_OGJ`_OGJ`c@^rr@Q~>
+JaVdjrr<2i!:K7Tk3hpCl0\NOrpKac#OVT[!!)HT!2T#_!<2Bd!<2Bd!;#XX!;#U]!:BjT!:B1S
+!<)<c!<)<c!<)<c!<)<c!42(r!:BjT!8m2L!:BjT!:K7TrU'XcJaS*WJaS*WJaW7"rr@Q~>
+Ja2Lbrr<2e!:&hLk3DX?l086Krp'I_#O2<S!!)<L!2Sl[!<26`!<26`!;#LT!;#IY!9sRL!:B%O
+!<)0_!<)0_!<)0_!<)0_!41qn!9sRL!8m&H!9sRL!:&hLrTX@_Ja.gOJa.gOJa2sorr@Q~>
+J`c4Z#6+\cs5rJDk2uC;l/i!GroX4[#Nl'Ks8V6Ds.ncWs8M-\s8M-\s7>CPs7>@Us5s@Ds6\qK
+s8D'[s8D'[s8D'[s8D'[s0Lhjs5s@Ds52rDs5s@Ds5rJDrT4+[J`_OGJ`_OGJ`c[grr@Q~>
+JaVgkrVuue!<2Bd!9<MH!9EPT!:BjT!:BjT!:BjdmJm6_mKN[ZmJu\C!;>jS!;Psd!:K7Tmd:)B
+mJm7dmJm7dmK*CV!<2Bd!<2Bd!3ttk!8m2M!:K7T!:BjT!<2Bd!1N<U!<)<c!<)<c!.jQhmXaeW
+mcX$<!.Y~>
+Ja2OcrVuua!<26`!9<AD!9EDP!9sRL!9sRL!9sR`l2Ug[l377Rl2^,7!;>^O!;Pg`!:&hLlKSB:
+l2Uh`l2Uh`l2gtN!<26`!<26`!3thg!8m&I!:&hL!9sRL!<26`!1N0Q!<)0_!<)0_!.jEdl@J5O
+lK@I4!.Y~>
+J`c7["TJH%k5YGC!<;*Arr;'A$NK8gk5XTDk5XTDroO4\Uuhm]k2u^+k5Y/;pAapM#63iJs5rJD
+mH+EKroO4\roO:^k5YGC!<<&\!<9=drr:m<#lj&Ls8V6]k5YGC!<8MM!<<#[!<<#[!<7W4J`_OG
+J`c.Xrr@Q~>
+JaVjl"T[rj!:BjcmJm7KmK<OX!:BjJmKim]mJm7T!:K7TrU'XcV!\?cmJuYTna?AVp$Me[rpBad
+rpBadmHt/Ymd:)CmK!7S"p")Z!:K7T[-mVfjR*!JrpC$lmJuYT!:K7TcL(Z4rU'XcrU'Xcf'WM<
+rpBadrpBadJaS*WJaS*Wk3i6LJ,~>
+Ja2Rd"T[ff!9sR_l2UhGl3%+P!9sRFl3RIUl2UhL!:&hLrTX@_V!8'_l2^)Ln`p)Rp$)MWrosI`
+rosI`mHOlUlKSB7l2^hK"p!rR!:&hL[-I>bjQZ^Frosahl2^)L!:&hLcKYB0rTX@_rTX@_f'358
+rosI`rosI`Ja.gOJa.gOk3DsHJ,~>
+J`c:\"om]b!9X=[joGGCjok_Hs5s@BjpD(Mk5YJDs5rJDrT4+[Uuhg[k5XTDn`KiNp#Z8SroO4\
+roO4\mH+WQk2u^+k5YGC#63iJs5rJD[-%)^jQ6IBroOLdk5XTDs5rJDcK5-,rT4+[rT4+[f&cu4
+roO4\roO4\J`_OGJ`_OGk2u[DJ,~>
+JaVdjrrCsE"p")Z!:K7Tk3`KTmJuYTmJuYTSF6@Vna6M[mJuYTp[/"]rU'XcrU'XcmHt)WmJuYT
+r9a[fmJuYTYO;AiiU-[GrU'pkmd:)C!:BgerU'Xcdd@)8rpBadrpBadeF!M@md:)CmJqh+JaS*W
+JaVXfrr@Q~>
+Ja2LbrrCsA"p!rR!:&hLk3<3Pl2^)Ll2^)LSEg(Rn`g5Wl2^)LpZ__YrTX@_rTX@_mHOfSl2^)L
+r9=Cbl2^)LYNl)eiT^CCrTXXglKSB7!9sOarTX@_dcpf4rosI`rosI`eER5<lKSB7l2ZD#Ja.gO
+Ja2@^rr@Q~>
+J`c4ZrrCs=#63iJs5rJDk2lsLk5XTDk5XTDSEBhNn`BuSk5XTDpZ;JUrT4+[rT4+[mH+QOk5XTD
+r8n.^k5XTDYNGiaiT:.?rT4Cck2u^+s5s@]rT4+[dcLQ0roO4\roO4\eE-u8k2u^+k5U"pJ`_OG
+J`c(Vrr@Q~>
+JaVdjrrD!F!!*#d!!*#d!!)6N!!*#drW)rc!!&_\p&OgS"p")Z!:K7Tf^A_=qX4=_YO2MnmJuYT
+g$\h>!U]semJm76mKN[ZmJu\C!7L9<!:BjT!.jQhmXaeWmc<g9!.Y~>
+Ja2LbrrD!B!!*#`!!*#`!!)6J!!*#`rW)r_!!&_Xp&OgO"p!rR!:&hLf]rG9qWe%[YNc5jl2^)L
+g$8P:!U9[al2Uh2l377Rl2^,7!7L-8!9sRL!.jEdl@J5OlK%71!.Y~>
+J`c4ZrrD!>!<<&\!<<&\!<;9F!<<&\rr;u[!<8bTpAajK#63iJs5rJDf]N25qW@eWYN>ufk5XTD
+g#i;6!TsF]joGG.jp(kJk5XT+s3g$4s5s@Ds+0<`k(2ZGk2b\)!.Y~>
+JaVdjrrD$G!!)uc!!)uc!!)9Op&LZOrW)QX!!*#d!!*#d!!(jCli=E\"p")Z!:K7ThX:%:cL(f8
+mJuYTd-gl5JaS*WJaS*Wj6lpIJ,~>
+Ja2LbrrD$C!!)u_!!)u_!!)9Kp&LZKrW)QT!!*#`!!*#`!!(j?li=EX"p!rR!:&hLhWjb6cKYN4
+l2^)Ld-CT1Ja.gOJa.gOj6HXEJ,~>
+J`c4ZrrD$?!<<#[!<<#[!<;<GpA^]Grr;TP!<<&\!<<&\!<:m;m/OHT#63iJs5rJDhWFM2cK590
+k5XTDd,t?-J`_OGJ`_OGj6$@AJ,~>
+JaVdjrrBXurW&DT"9@iimJuqJ!!)uc!!)uc!!(a@rW)f_rW'4k!!*#d!!*#d!!(dArW!&g!:Bj2
+mf!48me-X3mXaeWmXafhmf*9;~>
+Ja2LbrrBXqrW&DP"9@]el2^MB!!)u_!!)u_!!(a<rW)f[rW'4g!!*#`!!*#`!!(d=rW!&c!9sR.
+lM^e4lLk4/l@J5Ol@J6`lMgj7~>
+J`c4ZrrBXmrr8GL"TRWak5Y,:!<<#[!<<#[!<:d8rr;iWrr97c!<<&\!<<&\!<:g9rr3)_s5s@*
+k5PD0k4\h+k(2ZGk(2[Xk5PF3~>
+JaVdjrrB\!"9@iimJrdF"p")Z!:K7T`U3j/mJuYTr9a[fmJuYTrU'XcrU'XcrU'Xc_X7C(rU'Xc
+rU'Xch<k@FmJu\S!71*,!7UB8!.jQhmXaeWmc3a8!.Y~>
+Ja2LbrrB[r"9@]el2[@>"p!rR!:&hL`TdR+l2^)Lr9=Cbl2^)LrTX@_rTX@_rTX@__Wh+$rTX@_
+rTX@_h<G(Bl2^,K!70s(!7U64!.jEdl@J5OlJq10!.Y~>
+J`c4ZrrB[n"TRWak5Ut6#63iJs5rJD`T@='k5XTDr8n.^k5XTDrT4+[rT4+[rT4+[_WCjurT4+[
+rT4+[h<"h>k5XTCs3Kj$s3p-0s+0<`k(2ZGk2YV(!.Y~>
+JaVdjrrB_""p")Z!:K7TT'cRYrpBadrpBada6j-3md:)CmK!7S"p")Z!:K7TrU'XcrpBadrpBad
+POA)Dg$Sh?rU'XcrU0Xbd-_#:mJuYTJaS*WJaS*WjR3$JJ,~>
+Ja2LbrrB^s"p!rR!:&hLT'?:UrosI`rosI`a6Ej/lKSB7l2^hK"p!rR!:&hLrTX@_rosI`rosI`
+PNqf@g$/P;rTX@_rTa@^d-:`6l2^)LJa.gOJa.gOjQcaFJ,~>
+J`c4ZrrB^o#63iJs5rJDT&p%QroO4\roO4\a6!U+k2u^+k5YGC#63iJs5rJDrT4+[roO4\roO4\
+PNMQ<g#`;7rT4+[rT=+Zd,kK2k5XTDJ`_OGJ`_OGjQ?IBJ,~>
+JaVdjrrBb#!!*#d!!*#d!!&\[!!)uc!!)uc!!(./!!*#d!!*#d!W_WgrpBadrpBadrU'jimd:)C
+mJuD;!!)uc!!)uc!!'Rt!!*#d!<DQe!7^E:!<2Bd!<2Bh!:BjT!7L9>!:K7Tmd:(+meQ8N!<)<c
+!<)<c!.jQhmXag$mf*9;~>
+Ja2LbrrBat!!*#`!!*#`!!&\W!!)u_!!)u_!!(.+!!*#`!!*#`!W_KcrosI`rosI`rTXRelKSB7
+l2]u3!!)u_!!)u_!!'Rp!!*#`!<DEa!7^96!<26`!<26d!9sRL!7L-:!:&hLlKSA#lM9]F!<)0_
+!<)0_!.jEdl@J6qlMgj7~>
+J`c4ZrrBap!<<&\!<<&\!<8_S!<<#[!<<#[!<:1'!<<&\!<<&\!rqE_roO4\roO4\rT4=ak2u^+
+k5XT+!<<#[!<<#[!<9Ul!<<&\!WV<]s4$02s8M-\s8M-`s5s@Ds3g$6s5rJDk2u\pk5"->s8D'[
+s8D'[s+0<`k(2[ik5PF3~>
+JaVdjrrBe$!!)uc!!)uc!!'%e!!)uc!!)uc!!)0L!!)uc!!)uc!!)3M!!)uc!!)uc!!)uc!!)uc
+!!)uc"9@iimJu>9!!*#d!!*#d!!'Rt!!)ucrr</h!:Bj:mL0*`mJu\C!:K7Tmd:)@mJm7cmJm7c
+mJm7[mJm7dmJm7dmJm6<meQ8N!<2Bd!<2Bd!.jQhmXag#mf*9;~>
+Ja2LbrrBdu!!)u_!!)u_!!'%a!!)u_!!)u_!!)0H!!)u_!!)u_!!)3I!!)u_!!)u_!!)u_!!)u_
+!!)u_"9@]el2]o1!!*#`!!*#`!!'Rp!!)u_rr</d!9sR6l3m[Xl2^,7!:&hLlKSB8l2Uh_l2Uh_
+l2UhWl2Uh`l2Uh`l2Ug8lM9]F!<26`!<26`!.jEdl@J6plMgj7~>
+J`c4ZrrBdq!<<#[!<<#[!<9(]!<<#[!<<#[!<;3D!<<#[!<<#[!<;6E!<<#[!<<#[!<<#[!<<#[
+!<<#["TRWak5XN)!<<&\!<<&\!<9Ul!<<#[s8N2`s5s@2jp_:Pk5XT+s5rJDk2u^0joGG[joGG[
+joGGSjoGG\joGG\joGF4k5"->s8M-\s8M-\s+0<`k(2[hk5PF3~>
+JaVdjrr@W<g[5%ArpBadrpBadjR*!JrpBadrpBadbjPH1ipI!Nmd:)CmJsT]"p")Z!:K7TeF!G>
+mJuYTrpBadrpBadlg=`QrpBadrpBadp$Me[rU'XcrU'XcJaW@%"p")Z!:K7TJaS*WJaW7"rr@Q~>
+Ja2Lbrr@W8gZeb=rosI`rosI`jQZ^FrosI`rosI`bj,0-ip$^JlKSB7l2\0U"p!rR!:&hLeER/:
+l2^)LrosI`rosI`lfnHMrosI`rosI`p$)MWrTX@_rTX@_Ja3'r"p!rR!:&hLJa.gOJa2sorr@Q~>
+J`c4Zrr@W4gZAM9roO4\roO4\jQ6IBroO4\roO4\bi\p)ioUIFk2u^+k5VdM#63iJs5rJDeE-o6
+k5XTDroO4\roO4\lfJ3IroO4\roO4\p#Z8SrT4+[rT4+[J`cdj#63iJs5rJDJ`_OGJ`c[grr@Q~>
+JaVdjrr@W<g?o.Fmd:)CmJu87"p")Z!:K7TcgLK,jmE6OmJuYTZgIenrpBadrpBadeF*;9rU'Xc
+rU'Xclg=rWmd:)CmJqh+i9g^JmJuYTJaS*WJaW4!rr@Q~>
+Ja2Lbrr@W8g?JkBlKSB7l2]i/"p!rR!:&hLcg(3(jlusKl2^)LZg%MjrosI`rosI`eE[#5rTX@_
+rTX@_lfnZSlKSB7l2ZD#i9CFFl2^)LJa.gOJa2pnrr@Q~>
+J`c4Zrr@W4g?&V>k2u^+k5XH'#63iJs5rJDcfXs$jlQ^Gk5XTDZfV8froO4\roO4\eE6c1rT4+[
+rT4+[lfJEOk2u^+k5U"pi8t1Bk5XTDJ`_OGJ`cXfrr@Q~>
+h<t4ArpKddo'QJXfC&V<rpKacg$\k?JaV4Z"9@iimJu25"9@iimJtDtrW(mErW':m!!)uc!!)uc
+!!(^?p&O1A"9@iimJqh+hX:@CJaS*WJaW0urr@Q~>
+h<Oq=rp'L`o'-2TfBW>8rp'I_g$8S;Ja1qR"9@]el2]c-"9@]el2\ulrW(mArW':i!!)u_!!)u_
+!!(^;p&O1="9@]el2ZD#hWk(?Ja.gOJa2mmrr@Q~>
+h<+Y9roX4\o&]oPfB3&4roX1[g#i;7J`bYJ"TRWak5XB%"TRWak5WTdrr:p=rr9=e!<<#[!<<#[
+!<:a7pAa49"TRWak5U"phWFh;J`_OGJ`cUerr@Q~>
+kjAKPmK!%M!s%cW!<2Bd!:oOX!7pQ<!<2Bd!<2Bd!8?l@!.jRjmf!4Cmf!4/mK<OX!:BjKme-X3
+mJu\S!8m5D!.jRsme-Y\mJm7cmJm6<mXaeWmK2eV!.Y~>
+kir3Ll2^VE!s%WO!<26`!:oCT!7pE8!<26`!<26`!8?`<!.jFflM^e?lM^e+l3%+P!9sRGlLk4/
+l2^,K!8m)@!.jFolLk5Xl2Uh_l2Ug8l@J5Ol2p5N!.Y~>
+kiMpHjoG2=!s%KG!<2*\!:o7P!7p94!<2*\!<2*\!8?T8!.j:bk5PD;k5PD'jok_Hs5s@Ck4\h+
+joFQCs52u<s+0=kk4\iTjoGG[joGF4k(2ZGjoXZF!.Y~>
+kjAKPmK!%M!s%cW!<2Bd!:oOX!7pQ<!<2Bd!<2Bd!8?l@!.jRnme-YCme-Y,mKN[ZmJu\C!93GG
+!;c*`!<)<c!<)<c!0?ON!:BjT!9ESA!.jRsmf!4dmJm7dmJm7dmJm6<mXaeWmf2_T!.Y~>
+kir3Ll2^VE!s%WO!<26`!:oCT!7pE8!<26`!<26`!8?`<!.jFjlLk5?lLk5(l377Rl2^,7!93;C
+!;bs\!<)0_!<)0_!0?CJ!9sRL!9EG=!.jFolM^e`l2Uh`l2Uh`l2Ug8l@J5OlMp/L!.Y~>
+kiMpHjoG2=!s%KG!<2*\!:o7P!7p94!<2*\!<2*\!8?T8!.j:fk4\i;k4\i$jp(kJk5XT+s5N2?
+s8(jXs8D'[s8D'[s,Z:Fs5s@Ds5`>9s+0=kk5PD\joGG\joGG\joGF4k(2ZGk5XTD!.Y~>
+l0eHLq<e=bmd:)SmJm7cmf!4cmf!4dmK3IWmK!7SrW)c^r;cibrW)ud!s%`h!;Gm]!<2Bd!<2Bd
+!8?l@!.jRjmf!4Cmf!41mJm7dmJm7dmJm7JmK<OX!:Bj`mJm7dmJm7dmJm6OmJm7cmKN[ZmJuYT
+!9*AF!.jRpmL90a!:BjTmJu\C!:K7TJaS*WJaWO*rr@Q~>
+l0A0Hq<A%^lKSBKl2Uh_lM^e_lM^e`l2q%Ol2^hKrW)cZr;ci^rW)u`!s%Td!;GaY!<26`!<26`
+!8?`<!.jFflM^e?lM^e-l2Uh`l2Uh`l2UhFl3%+P!9sR\l2Uh`l2Uh`l2UgKl2Uh_l377Rl2^)L
+!9*5B!.jFll4!aY!9sRLl2^,7!:&hLJa.gOJa37"rr@Q~>
+l/qmDq;qbZk2l[Cjo>D[k5GA[k5GA\joYVGjoGDCrW)cVr;ciZrW)u\!s%H`!;GUU!<2*\!<2*\
+!8?T8!.j:bk5PD;k5PD)joGG\joGG\joGGBjok_Hs5s@XjoGG\joGG\joGFGjoGG[jp(kJk5XTD
+s5E,>s+0=hjph@Qs5s@Dk5XT+s5rJDJ`_OGJ`csorr@Q~>
+kjAKPmK!%MrW)ob!!*#d!!*#d!W_WgrpBmhmJuYTrpBadrpBadqX+=`rpBgfmK!7S"p"&k!:K7T
+q!J+^rpBadrpBadg@"t@JaV4Z"9@iimJu25"9@iimJtQ#!!)uc!!)uc!!)0L"p")Z!:K7TqX+Of
+md:)CmJrF<$NTV_!!)HT!!)HT!9<JM!:BjT!.jRrmL90amJu\C!:K7TmJuYTJaS*WJaWL)rr@Q~>
+kir3Ll2^VErW)o^!!*#`!!*#`!W_KcrosUdl2^)LrosI`rosI`qW\%\rosObl2^hK"p!og!:&hL
+q!%hZrosI`rosI`g?S\<Ja1qR"9@]el2]c-"9@]el2],p!!)u_!!)u_!!)0H"p!rR!:&hLqW\7b
+lKSB7l2["4$NTJW!!)<L!!)<L!9<>I!9sRL!.jFnl4!aYl2^,7!:&hLl2^)LJa.gOJa34!rr@Q~>
+kiMpHjoG2=rW)oZ!!*#\!!*#\!W_?_roO=`joFNDroO1\roO1\qW7bXroO7^joGDC"p!cc!9WDD
+puVPVroO1\roO1\g?/D8J`bYJ"TRWak5XB%"TRWak5W`h!<<#[!<<#[!<;3D#63iJs5rJDqW8"^
+k2u^+k5UV,$ifAOs8V6Ds8V6Ds5W5Es5s@Ds+0=jjph@Qk5XT+s5rJDk5XTDJ`_OGJ`cpnrr@Q~>
+l0eHLq<e=bmd:)SmJm7dmJm7dmK*CV!;u9b!;u9b!;Ps^!<2Be!:Kab!U]s[mJm7dmJm7dmJm7@
+mf*9<mb7(5!:K7Tmd:)7mKN[ZmJu\C!35Ge!<2Bd!<2Bd!;c*d!:BjT!0HXK!!M]imd:)RmJm7c
+mJm7cmJm7UmKN[ZmJu\C!.jRtmJm7dmJm7dmK*CVmf!3;mXaeWmelMQ!.Y~>
+l0A0Hq<A%^lKSBKl2Uh`l2Uh`l2gtN!;u-^!;u-^!;PgZ!<26a!:'I^!U9[Wl2Uh`l2Uh`l2Uh<
+lMgj8lItM-!:&hLlKSB/l377Rl2^,7!35;a!<26`!<26`!;bs`!9sRL!0HLG!!MQelKSBJl2Uh_
+l2Uh_l2UhQl377Rl2^,7!.jFpl2Uh`l2Uh`l2gtNlM^d7l@J5OlMTrI!.Y~>
+l/qmDq;qbZk2l[Cjo>D\jo>D\joPPF!;u!Z!;u!Z!;P[V!<2*]!9X1Z!TjCSjo>D\jo>D\jo>D8
+k5PF4k1\r%s5rJDk2u^'jp(kJk5XT+s/P2]s8M-\s8M-\s8(j\s5s@Ds,cCCrrhKak2u^BjoGG[
+joGG[joGGMjp(kJk5XT+s+0=ljoGG\joGG\joYSFk5PC3k(2ZGk5=BA!.Y~>
+kjAKPmK!%M!s%cW!<2Bd!<2Bd!<2Bf!:BjbmK*CV!;l0a!;Z$_!<2Bf!:BjbmJm7ZmJm7dmJm7d
+mJm7@mf*9<mb@.0!<2Bd!<2Bd!9EPJ!<2Bd!<2Bd!3GSg!<)<c!<)<c!;c-_!6":*!<)<c!<)<c
+!7pQ@!:BjT!;l0a!<2Bd!<2Bd!:T=U!<2Bd!<2Bd!.jS!mJm7cmK*CVme-X3mXaeWmK2eV!.Y~>
+kir3Ll2^VE!s%WO!<26`!<26`!<26b!9sR^l2gtN!;l$]!;Ym[!<26b!9sR^l2UhVl2Uh`l2Uh`
+l2Uh<lMgj8lJ(S(!<26`!<26`!9EDF!<26`!<26`!3GGc!<)0_!<)0_!;c![!6".&!<)0_!<)0_
+!7pE<!9sRL!;l$]!<26`!<26`!:T1Q!<26`!<26`!.jFrl2Uh_l2gtNlLk4/l@J5Ol2p5N!.Y~>
+kiMpHjoG2=!s%KG!<2*\!<2*\!<2*^!9O:ZjoPPF!;kmY!;YaW!<2*^!9O:Zjo>DRjo>D\jo>D\
+jo>D8k5PF4k1f"us8M-\s8M-\s5`;Bs8M-\s8M-\s/b>_s8D'[s8D'[s8(mWs2=%"s8D'[s8D'[
+s46<8s5s@Ds81pYs8M-\s8M-\s6o(Ms8M-\s8M-\s+0=njoGG[joYSFk4\h+k(2ZGjoXZF!.Y~>
+kjAKPmK!%M!s%cW!<2Bd!<2Bd!<2Bf!:BjdmK`g\!:K7Tmd:)SmJm7`mem.cmJm7dmK*CV!;,[Z
+!<2Bd!<2Bd!8?l@!.jRnmJm7cmJm7cmJm7LmJm7cmJm7cmJm6Zme-Y\mJm7cmJm7cmJm7:mJm7d
+mJm7dmJm7:mf!4^mKN[ZmJu\C!:T=U!<)<c!<)<c!.jRkmf!3;mXaeWmelMQ!.Y~>
+kir3Ll2^VE!s%WO!<26`!<26`!<26b!9sR`l3ICT!:&hLlKSBKl2Uh\lMU__l2Uh`l2gtN!;,OV
+!<26`!<26`!8?`<!.jFjl2Uh_l2Uh_l2UhHl2Uh_l2Uh_l2UgVlLk5Xl2Uh_l2Uh_l2Uh6l2Uh`
+l2Uh`l2Uh6lM^eZl377Rl2^,7!:T1Q!<)0_!<)0_!.jFglM^d7l@J5OlMTrI!.Y~>
+kiMpHjoG2=!s%KG!<2*\!<2*\!<2*^!9O:\jp1tL!9WDDk2l[Cjo>DXk5>;[jo>D\joPPF!;,CR
+!<2*\!<2*\!8?T8!.j:fjoGG[joGG[joGGDjoGG[joGG[joGFRk4\iTjoGG[joGG[joGG2joGG\
+joGG\joGG2k5PDVjp(kJk5XT+s6o(Ms8D'[s8D'[s+0=ck5PC3k(2ZGk5=BA!.Y~>
+h<t4ArpKacrpKacrpKacs6]jerpBgfmdC&Sq<e4_qsOF`s6]jep$VeZrpKacg$\k?JaS*W`U<^*
+qX+=`rpBadrpBaddd@;>md:)CmJtl,p&P$Y"9@iimJqh+]^>n&mJuYTJaS*WJaWL)rr@Q~>
+h<Oq=rp'I_rp'I_rp'I_s69RarosOblK\?Kq<@q[qs+.\s69Rap$2MVrp'I_g$8S;Ja.gO`TmF&
+qW\%\rosI`rosI`dcq#:lKSB7l2]H$p&P$U"9@]el2ZD#]]oV"l2^)LJa.gOJa34!rr@Q~>
+h<+Y9roX1[roX1[roX1[s5j:]roO7^k2uXCq;qYWqr[kXs5j:]p#c5RroX1[g#i;7J`_OG`TI1"
+qW7eXroO4\roO4\dcLc6k2u^+k5X&qpAb'Q"TRWak5U"p]]K@sk5XTDJ`_OGJ`cpnrr@Q~>
+ZgIen\F0@sJaS*W`pNs0mJuYTqX+Ofmd:)CmJtW%"9@iimJt]'rW)]\rW%N;]^>t(md:)CmJqh+
+JaS*WrpKddJ,~>
+Zg%Mj\Ea(oJa.gO`p*[,l2^)LqW\7blKSB7l2]2r"9@]el2]8trW)]XrW%N7]]o\$lKSB7l2ZD#
+Ja.gOrp'L`J,~>
+ZfV5f\E<ekJ`_OG`o[F(k5XTDqW8"^k2u^+k5Wfj"TRWak5Wllrr;`Trr7Q3]]KFuk2u^+k5U"p
+J`_OGroX4\J,~>
+ZgIen\F0@sJaS*Wa6j-3md:)CmK!+O"9@iimJtQ#rW(C7"9@iimK!1Qp&K[3_<q:'rpBadrpBad
+JaS*WJaWR+rr@Q~>
+Zg%Mj\Ea(oJa.gOa6Ej/lKSB7l2^\G"9@]el2],prW(C3"9@]el2^bIp&K[/_<M"#rosI`rosI`
+Ja.gOJa3:#rr@Q~>
+ZfV5f\E<ekJ`_OGa6!U+k2u^+k5Y;?"TRWak5W`hrr:F/"TRWak5YAApA]^+_<(atroO4\roO4\
+J`_OGJ`d!prr@Q~>
+JaVdjrr@W<JaUVI!!*#d!!*#d!!)l`rW(@6p&N_4"p")Z!:K7Tq<n4^JaU8?!!)uc!!)uc!!%T<
+JaS*W!q$$gJ,~>
+Ja2Lbrr@W8Ja1>A!!*#`!!*#`!!)l\rW(@2p&N_0"p!rR!:&hLq<IqZJa0u7!!)u_!!)u_!!%T8
+Ja.gO!pTacJ,~>
+J`c4Zrr@W4J`b&9!<<&\!<<&\!<;oXrr:C.pA`b,#63iJs5rJDq<%\VJ`a]/!<<#[!<<#[!<7W4
+J`_OG!p0I_J,~>
+JaVdjrr@W<JaUYJ!!)uc!!)uc!W_ZVp&NM.rW(I9!!*#d!!*#d!!)oa"9@iimJti+!!)uc!!)uc
+!!%T<JaS*WJaVdjrr@Q~>
+Ja2Lbrr@W8Ja1AB!!)u_!!)u_!W_NNp&NM*rW(I5!!*#`!!*#`!!)o]"9@]el2]E#!!)u_!!)u_
+!!%T8Ja.gOJa2Lbrr@Q~>
+J`c4Zrr@W4J`b):!<<#[!<<#[!rqEFpA`P&rr:L1!<<&\!<<&\!<;rY"TRWak5X#p!<<#[!<<#[
+!<7W4J`_OGJ`c4Zrr@Q~>
+JaVdjrr@W<JaU&9rW(73"9@iimJti+!!)uc!!)uc!!)uc"p")Z!:K7TlL"WPrU'XcrU'Xcq!J+^
+rpBadrpBadJaS*WJaS*WkjJHNJ,~>
+Ja2Lbrr@W8Ja0c1rW(7/"9@]el2]E#!!)u_!!)u_!!)u_"p!rR!:&hLlKS?LrTX@_rTX@_q!%hZ
+rosI`rosI`Ja.gOJa.gOkj&0JJ,~>
+J`c4Zrr@W4J`aK)rr::+"TRWak5X#p!<<#[!<<#[!<<#[#63iJs5rJDlK/*HrT4+[rT4+[puVSV
+roO4\roO4\J`_OGJ`_OGkiVmFJ,~>
+JaVdjrr@W<JaU):"9@iimJtW%"p")Z!:K7TamK-/rpBadrpBadlL"WPrpBadrpBadp?i+bmd:)C
+mJqh+JaS*WJaV^hrr@Q~>
+Ja2Lbrr@W8Ja0f2"9@]el2]2r"p!rR!:&hLam&j+rosI`rosI`lKS?LrosI`rosI`p?Dh^lKSB7
+l2ZD#Ja.gOJa2F`rr@Q~>
+J`c4Zrr@W4J`aN*"TRWak5Wfj#63iJs5rJDalWU'roO4\roO4\lK/*HroO4\roO4\p>uSZk2u^+
+k5U"pJ`_OGJ`c.Xrr@Q~>
+JaVdjrr@W<JaU,;"p")Z!:K7Tdd@)8rpBadrpBadbO,?1rU'XcrU'XclL"iVmd:)CmJunI"9@ii
+mJqh+JaS*WJaV[grr@Q~>
+Ja2Lbrr@W8Ja0i3"p!rR!:&hLdcpf4rosI`rosI`bN]'-rTX@_rTX@_lKSQRlKSB7l2^JA"9@]e
+l2ZD#Ja.gOJa2C_rr@Q~>
+J`c4Zrr@W4J`aQ+#63iJs5rJDdcLQ0roO4\roO4\bN8g)rT4+[rT4+[lK/<Nk2u^+k5Y)9"TRWa
+k5U"pJ`_OGJ`c+Wrr@Q~>
+JaVdjrr@W<JaU/<!!*#d!!*#d!!)l`!!)uc!!)uc!!)0L!!)uc!!)uc!!'"d"9@iimJuhGrW%N;
+JaS*WJaVXfrr@Q~>
+Ja2Lbrr@W8Ja0l4!!*#`!!*#`!!)l\!!)u_!!)u_!!)0H!!)u_!!)u_!!'"`"9@]el2^D?rW%N7
+Ja.gOJa2@^rr@Q~>
+J`c4Zrr@W4J`aT,!<<&\!<<&\!