Merge branch 'v2/master'

Conflicts:
	doc/rteval.8
	rteval.spec
	rteval/cyclictest.py
	rteval/hackbench.py
	rteval/rteval.conf
	rteval/rteval.py
	setup.py

Signed-off-by: Clark Williams <williams@redhat.com>
diff --git a/.gitignore b/.gitignore
index 46ee31a..85e7494 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
 rpm
 tarball
 *.bz2
+*.gz
 *.rpt
 *.xml
 *.o
@@ -20,3 +21,14 @@
 build
 rpm*
 usr
+dist/
+MANIFEST
+.deps
+stamp-h1
+autom4te.cache/
+server/Makefile
+server/config.log
+server/config.status
+server/parser/Makefile
+server/parser/config.h
+rteval-[0-9]*
diff --git a/COPYING b/COPYING
index 8a2927c..d159169 100644
--- a/COPYING
+++ b/COPYING
@@ -1,13 +1,12 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
 
-		    GNU GENERAL PUBLIC LICENSE
-		       Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-                          675 Mass Ave, Cambridge, MA 02139, USA
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
-			    Preamble
+                            Preamble
 
   The licenses for most software are designed to take away your
 freedom to share and change it.  By contrast, the GNU General Public
@@ -16,7 +15,7 @@
 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
+the GNU Lesser General Public License instead.)  You can apply it to
 your programs, too.
 
   When we speak of free software, we are referring to freedom, not
@@ -56,8 +55,8 @@
 
   The precise terms and conditions for copying, distribution and
 modification follow.
-
-		    GNU GENERAL PUBLIC LICENSE
+
+                    GNU GENERAL PUBLIC LICENSE
    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
   0. This License applies to any program or other work which contains
@@ -111,7 +110,7 @@
     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
@@ -169,7 +168,7 @@
 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
@@ -226,7 +225,7 @@
 
 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
@@ -256,7 +255,7 @@
 of preserving the free status of all derivatives of our free software and
 of promoting the sharing and reuse of software generally.
 
-			    NO WARRANTY
+                            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
@@ -278,9 +277,9 @@
 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
+                     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
@@ -292,7 +291,7 @@
 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) 19yy  <name of author>
+    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
@@ -304,16 +303,16 @@
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
 
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+    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.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) 19yy name of author
+    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.
@@ -336,489 +335,5 @@
 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
+library.  If this is what you want to do, use the GNU Lesser General
 Public License instead of this License.
-
----------------------------------------------------------------------------
-
-		  GNU LIBRARY GENERAL PUBLIC LICENSE
-		       Version 2, June 1991
-
- Copyright (C) 1991 Free Software Foundation, Inc.
-                    675 Mass Ave, Cambridge, MA 02139, USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-[This is the first released version of the library GPL.  It is
- numbered 2 because it goes with version 2 of the ordinary GPL.]
-
-			    Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
-
-  This license, the Library General Public License, applies to some
-specially designated Free Software Foundation software, and to any
-other libraries whose authors decide to use it.  You can use it for
-your libraries, 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 library, or if you modify it.
-
-  For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you.  You must make sure that they, too, receive or can get the source
-code.  If you link a program with the library, you must provide
-complete object files to the recipients so that they can relink them
-with the library, after making changes to the library and recompiling
-it.  And you must show them these terms so they know their rights.
-
-  Our method of protecting your rights has two steps: (1) copyright
-the library, and (2) offer you this license which gives you legal
-permission to copy, distribute and/or modify the library.
-
-  Also, for each distributor's protection, we want to make certain
-that everyone understands that there is no warranty for this free
-library.  If the library is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original
-version, 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 companies distributing free
-software will individually obtain patent licenses, thus in effect
-transforming the program into proprietary software.  To prevent this,
-we have made it clear that any patent must be licensed for everyone's
-free use or not licensed at all.
-
-  Most GNU software, including some libraries, is covered by the ordinary
-GNU General Public License, which was designed for utility programs.  This
-license, the GNU Library General Public License, applies to certain
-designated libraries.  This license is quite different from the ordinary
-one; be sure to read it in full, and don't assume that anything in it is
-the same as in the ordinary license.
-
-  The reason we have a separate public license for some libraries is that
-they blur the distinction we usually make between modifying or adding to a
-program and simply using it.  Linking a program with a library, without
-changing the library, is in some sense simply using the library, and is
-analogous to running a utility program or application program.  However, in
-a textual and legal sense, the linked executable is a combined work, a
-derivative of the original library, and the ordinary General Public License
-treats it as such.
-
-  Because of this blurred distinction, using the ordinary General
-Public License for libraries did not effectively promote software
-sharing, because most developers did not use the libraries.  We
-concluded that weaker conditions might promote sharing better.
-
-  However, unrestricted linking of non-free programs would deprive the
-users of those programs of all benefit from the free status of the
-libraries themselves.  This Library General Public License is intended to
-permit developers of non-free programs to use free libraries, while
-preserving your freedom as a user of such programs to change the free
-libraries that are incorporated in them.  (We have not seen how to achieve
-this as regards changes in header files, but we have achieved it as regards
-changes in the actual functions of the Library.)  The hope is that this
-will lead to faster development of free libraries.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.  Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library".  The
-former contains code derived from the library, while the latter only
-works together with the library.
-
-  Note that it is possible for a library to be covered by the ordinary
-General Public License rather than by this special one.
-
-		  GNU LIBRARY GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License Agreement applies to any software library which
-contains a notice placed by the copyright holder or other authorized
-party saying it may be distributed under the terms of this Library
-General Public License (also called "this License").  Each licensee is
-addressed as "you".
-
-  A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
-
-  The "Library", below, refers to any such software library or work
-which has been distributed under these terms.  A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language.  (Hereinafter, translation is
-included without limitation in the term "modification".)
-
-  "Source code" for a work means the preferred form of the work for
-making modifications to it.  For a library, 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 library.
-
-  Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it).  Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-  
-  1. You may copy and distribute verbatim copies of the Library's
-complete 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 distribute a copy of this License along with the
-Library.
-
-  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 Library or any portion
-of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
-
-    b) You must cause the files modified to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    c) You must cause the whole of the work to be licensed at no
-    charge to all third parties under the terms of this License.
-
-    d) If a facility in the modified Library refers to a function or a
-    table of data to be supplied by an application program that uses
-    the facility, other than as an argument passed when the facility
-    is invoked, then you must make a good faith effort to ensure that,
-    in the event an application does not supply such function or
-    table, the facility still operates, and performs whatever part of
-    its purpose remains meaningful.
-
-    (For example, a function in a library to compute square roots has
-    a purpose that is entirely well-defined independent of the
-    application.  Therefore, Subsection 2d requires that any
-    application-supplied function or table used by this function must
-    be optional: if the application does not supply it, the square
-    root function must still compute square roots.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Library,
-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 Library, 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 Library.
-
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library.  To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License.  (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.)  Do not make any other change in
-these notices.
-
-  Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
-
-  This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
-
-  4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you 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.
-
-  If distribution of 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 satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library".  Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
-
-  However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library".  The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
-  When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library.  The
-threshold for this to be true is not precisely defined by law.
-
-  If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work.  (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
-  Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
-
-  6. As an exception to the Sections above, you may also compile or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
-
-  You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License.  You must supply a copy of this License.  If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License.  Also, you must do one
-of these things:
-
-    a) Accompany the work with the complete corresponding
-    machine-readable source code for the Library including whatever
-    changes were used in the work (which must be distributed under
-    Sections 1 and 2 above); and, if the work is an executable linked
-    with the Library, with the complete machine-readable "work that
-    uses the Library", as object code and/or source code, so that the
-    user can modify the Library and then relink to produce a modified
-    executable containing the modified Library.  (It is understood
-    that the user who changes the contents of definitions files in the
-    Library will not necessarily be able to recompile the application
-    to use the modified definitions.)
-
-    b) Accompany the work with a written offer, valid for at
-    least three years, to give the same user the materials
-    specified in Subsection 6a, above, for a charge no more
-    than the cost of performing this distribution.
-
-    c) If distribution of the work is made by offering access to copy
-    from a designated place, offer equivalent access to copy the above
-    specified materials from the same place.
-
-    d) Verify that the user has already received a copy of these
-    materials or that you have already sent this user a copy.
-
-  For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it.  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.
-
-  It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system.  Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-
-  7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
-    a) Accompany the combined library with a copy of the same work
-    based on the Library, uncombined with any other library
-    facilities.  This must be distributed under the terms of the
-    Sections above.
-
-    b) Give prominent notice with the combined library of the fact
-    that part of it is a work based on the Library, and explaining
-    where to find the accompanying uncombined form of the same work.
-
-  8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License.  Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library 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.
-
-  9. 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 Library or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
-
-  10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-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.
-
-  11. 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 Library at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Library 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 Library.
-
-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.
-
-  12. If the distribution and/or use of the Library is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library 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.
-
-  13. The Free Software Foundation may publish revised and/or new
-versions of the Library 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 Library
-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 Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-
-  14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-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
-
-  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "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
-LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. 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 LIBRARY 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
-LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
-
-		     END OF TERMS AND CONDITIONS
-
-     Appendix: How to Apply These Terms to Your New Libraries
-
-  If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change.  You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms of the
-ordinary General Public License).
-
-  To apply these terms, attach the following notices to the library.  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 library's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This library is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Library General Public
-    License as published by the Free Software Foundation; either
-    version 2 of the License, or (at your option) any later version.
-
-    This library 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
-    Library General Public License for more details.
-
-    You should have received a copy of the GNU Library General Public
-    License along with this library; if not, write to the Free
-    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the library, if
-necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the
-  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
-
-  <signature of Ty Coon>, 1 April 1990
-  Ty Coon, President of Vice
-
-That's all there is to it!
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..82560a8
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include COPYING MANIFEST.in rteval-cmd
+recursive-include doc *.? *.txt
+exclude dist/rteval dist/rteval.8.gz
diff --git a/Makefile b/Makefile
index d373860..4392d5f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,29 +1,10 @@
 HERE	:=	$(shell pwd)
 PACKAGE :=	rteval
-VERSION :=      $(shell awk '/Version:/ { print $$2 }' ${PACKAGE}.spec | head -n 1)
+VERSION :=      $(shell python -c "from rteval import RTEVAL_VERSION; print RTEVAL_VERSION")
 D	:=	10
-PYSRC	:=	rteval/rteval.py 	\
-		rteval/cputopology.py	\
-		rteval/cyclictest.py 	\
-		rteval/dmi.py 		\
-		rteval/hackbench.py 	\
-		rteval/__init__.py 	\
-		rteval/kcompile.py 	\
-		rteval/load.py 		\
-		rteval/rtevalclient.py 	\
-		rteval/rtevalConfig.py 	\
-		rteval/rtevalMailer.py 	\
-		rteval/util.py		\
-		rteval/xmlout.py
-
-XSLSRC	:=	rteval/rteval_dmi.xsl 	\
-		rteval/rteval_text.xsl  \
-		rteval/rteval_histogram_raw.xsl 
-
-CONFSRC	:=	rteval/rteval.conf
 
 # XML-RPC related files
-XMLRPCVER := 1.5
+XMLRPCVER := 1.6
 XMLRPCDIR := server
 
 DESTDIR	:=
@@ -34,26 +15,28 @@
 PYLIB	:= 	$(DESTDIR)$(shell python -c 'import distutils.sysconfig;  print distutils.sysconfig.get_python_lib()')
 LOADDIR	:=	loadsource
 
-KLOAD	:=	$(LOADDIR)/linux-2.6.39.tar.bz2
+KLOAD	:=	$(LOADDIR)/linux-4.9.tar.xz
 BLOAD	:=	$(LOADDIR)/dbench-4.0.tar.gz
 LOADS	:=	$(KLOAD) $(BLOAD)
 
 runit:
-	[ -d ./run ] || mkdir run
-	python rteval/rteval.py -D -L -v --workdir=./run --loaddir=./loadsource --duration=$(D) -f ./rteval/rteval.conf -i ./rteval
+	[ -d $(HERE)/run ] || mkdir run
+	python rteval-cmd -D -L -v --workdir=$(HERE)/run --loaddir=$(HERE)/loadsource --duration=$(D) -f $(HERE)/rteval.conf -i $(HERE)/rteval $(EXTRA)
 
 load:
 	[ -d ./run ] || mkdir run
-	python rteval/rteval.py --onlyload -D -L -v --workdir=./run --loaddir=./loadsource -f ./rteval/rteval.conf -i ./rteval
+	python rteval-cmd --onlyload -D -L -v --workdir=./run --loaddir=$(HERE)/loadsource -f $(HERE)/rteval/rteval.conf -i $(HERE)/rteval
+
 sysreport:
-	python rteval/rteval.py -D -v --workdir=./run --loaddir=./loadsource --duration=$(D) -i ./rteval --sysreport
+	python rteval-cmd -D -v --workdir=$(HERE)/run --loaddir=$(HERE)/loadsource --duration=$(D) -i $(HERE)/rteval --sysreport
 
 clean:
+	[ -f $(XMLRPCDIR)/Makefile ] && make -C $(XMLRPCDIR) clean || echo -n
 	rm -f *~ rteval/*~ rteval/*.py[co] *.tar.bz2 *.tar.gz doc/*~ server/rteval-xmlrpc-*.tar.gz
 
 realclean: clean
 	[ -f $(XMLRPCDIR)/Makefile ] && make -C $(XMLRPCDIR) maintainer-clean || echo -n
-	rm -rf run tarball rpm
+	rm -rf run rpm
 
 install: install_loads install_rteval
 
@@ -63,14 +46,6 @@
 	else \
 		python setup.py install --root=$(DESTDIR); \
 	fi
-	install -m 644 rteval/rteval_text.xsl $(DATADIR)/rteval
-	install -m 644 rteval/rteval_dmi.xsl $(DATADIR)/rteval
-	install -m 644 rteval/rteval_histogram_raw.xsl $(DATADIR)/rteval
-	install -m 644 rteval/rteval.conf $(CONFDIR)
-	install -m 644 doc/rteval.8 $(MANDIR)/man8/
-	gzip -f $(MANDIR)/man8/rteval.8
-	chmod 755 $(PYLIB)/rteval/rteval.py
-#	ln -s $(PYLIB)/rteval/rteval.py $(DESTDIR)/usr/bin/rteval;
 
 install_loads:	$(LOADS)
 	[ -d $(DATADIR)/rteval/loadsource ] || mkdir -p $(DATADIR)/rteval/loadsource
@@ -92,14 +67,12 @@
 	rm -rf $(PYLIB)/rteval
 	rm -rf $(DATADIR)/rteval
 
-tarfile:
-	rm -rf tarball && mkdir -p tarball/rteval-$(VERSION)/rteval tarball/rteval-$(VERSION)/server tarball/rteval-$(VERSION)/sql
-	cp $(PYSRC) tarball/rteval-$(VERSION)/rteval
-	cp $(XSLSRC) tarball/rteval-$(VERSION)/rteval
-	cp $(CONFSRC) tarball/rteval-$(VERSION)/rteval
-	cp -r doc/ tarball/rteval-$(VERSION)
-	cp Makefile setup.py rteval.spec COPYING tarball/rteval-$(VERSION)
-	tar -C tarball -cjvf rteval-$(VERSION).tar.bz2 rteval-$(VERSION)
+tarfile: rteval-$(VERSION).tar.bz2
+
+rteval-$(VERSION).tar.bz2:
+	python setup.py sdist --formats=bztar --owner root --group root
+	mv dist/rteval-$(VERSION).tar.bz2 .
+	rmdir dist
 
 rteval-xmlrpc-$(XMLRPCVER).tar.gz :
 	cd $(XMLRPCDIR) ;             \
@@ -112,19 +85,30 @@
 	rm -rf rpm
 	mkdir -p rpm/{BUILD,RPMS,SRPMS,SOURCES,SPECS}
 
-rpms rpm: rpm_prep rtevalrpm loadrpm xmlrpcrpm
+rpms rpm: rpm_prep rtevalrpm loadrpm
 
-rtevalrpm: tarfile
-	cp rteval-$(VERSION).tar.bz2 rpm/SOURCES
+rtevalrpm: rteval-$(VERSION).tar.bz2
+	cp $^ rpm/SOURCES
 	cp rteval.spec rpm/SPECS
 	rpmbuild -ba --define "_topdir $(HERE)/rpm" rpm/SPECS/rteval.spec
 
+rtevalsrpm: rteval-$(VERSION).tar.bz2
+	cp $^ rpm/SOURCES
+	cp rteval.spec rpm/SPECS
+	rpmbuild -bs --define "_topdir $(HERE)/rpm" rpm/SPECS/rteval.spec
+
+
 xmlrpcrpm: rteval-xmlrpc-$(XMLRPCVER).tar.gz
 	cp rteval-xmlrpc-$(XMLRPCVER).tar.gz rpm/SOURCES/
 	cp server/rteval-parser.spec rpm/SPECS/
 	rpmbuild -ba --define "_topdir $(HERE)/rpm" rpm/SPECS/rteval-parser.spec
 
-loadrpm: 
+xmlsrpm: rteval-xmlrpc-$(XMLRPCVER).tar.gz
+	cp rteval-xmlrpc-$(XMLRPCVER).tar.gz rpm/SOURCES/
+	cp server/rteval-parser.spec rpm/SPECS/
+	rpmbuild -bs --define "_topdir $(HERE)/rpm" rpm/SPECS/rteval-parser.spec
+
+loadrpm:
 	rm -rf rpm-loads
 	mkdir -p rpm-loads/{BUILD,RPMS,SRPMS,SOURCES,SPECS}
 	cp rteval-loads.spec rpm-loads/SPECS
diff --git a/README b/README
index feabf10..62b1cd2 100644
--- a/README
+++ b/README
@@ -9,7 +9,7 @@
 timer latency values in histogram format, which is analyzed by
 rteval. Rteval then writes an XML file to disk with information about
 the system (gotten through DMI tables), the raw histogram data
-collected during the run and the statistical analysis of the run. 
+collected during the run and the statistical analysis of the run.
 
 The rteval source may be pulled from it's git tree on kernel.org:
     git://git.kernel.org/pub/scm/linux/kernel/git/clrkwllms/rteval.git
@@ -19,25 +19,24 @@
 Python >= 2.4 (and < 3.0)
     http://www.python.org/download/
 
-python-schedtils
+python-schedutils
     git://git.kernel.org/pub/scm/linux/kernel/git/acme/python-schedutils.git
 
-python-ethtools
+python-ethtool
     git://git.kernel.org/pub/scm/linux/kernel/git/acme/python-ethtool.git
 
-libxslt-python
-    http://xmlsoft.org/XSLT/python.html
+python-lxml
+  http://lxml.de/
 
 python-dmidecode
     http://www.ohloh.net/p/python-dmidecode
 
-rt-tests
-   git://git.kernel.org/pub/scm/linux/kernel/git/clrkwllms/rt-tests.git
+libxml2-python
+  http://xmlsoft.org/
 
+rt-tests
+  git://git.kernel.org/pub/scm/utils/rt-tests/rt-tests.git
 
 Clark Williams <williams@redhat.com> wrote rteval
 David Sommerseth <davids@redhat.com> wrote the XML-RPC and database
-     logic for handling rteval results. 
-
-
-
+     logic for handling rteval results.
diff --git a/doc/installing.txt b/doc/installing.txt
index c202902..c592e9c 100644
--- a/doc/installing.txt
+++ b/doc/installing.txt
@@ -9,14 +9,18 @@
   A python library to query network interfaces
   git://git.kernel.org/pub/scm/linux/kernel/git/acme/python-ethtool.git
 
-libxslt-python
-  A python library to parse XSLT stylesheets
-  http://www.xmlsoft.org/XSLT/python.html
+python-lxml
+  A python library to parse XML files and XSLT stylesheets
+  http://lxml.de/
 
 python-dmidecode
   A python library used to access DMI table information
   http://www.autonomy.net.au/display/pydmi/Home
 
+libxml2-python
+  A python library to parse XML files
+  http://xmlsoft.org/
+
 rt-tests
   A collection of programs used to measure real-time behavior
   git://git.kernel.org/pub/scm/linux/kernel/git/clrkwllms/rt-tests.git
diff --git a/doc/release-checklist.txt b/doc/release-checklist.txt
index 1eed9a4..e9d99dd 100644
--- a/doc/release-checklist.txt
+++ b/doc/release-checklist.txt
@@ -2,7 +2,7 @@
 # set of steps for releasing a new version of rteval
 #
 # Note:
-#     git commits should have a summary line followed by 
+#     git commits should have a summary line followed by
 #     blank line and then the detailed info on the commit
 #     followed by the committer's Signed-off-by line
 #
@@ -13,10 +13,10 @@
 4. Merge any remote branch updates into 'work'
 5. Merge any local branch updates into 'work'
 6. Merge 'work' back into 'master'
-7. Update version number in rteval/rteval.py, setup.py and rteval.spec
+7. Update version number in rteval/version.py and rteval.spec
 8. Update %changelog info in rteval.spec
-9. Commit rteval/rteval.py, setup.py and rteval.spec changes
+9. Commit rteval/version.py and rteval.spec changes
 10. Create signed tag of the form v<major>.<minor> for the release:
-    e.g.:  git tag -u williams@redhat.com  v1.10
+    e.g.:  git tag -u <your-rh-login>@redhat.com  v1.10
 11. Push master branch back to origin
     e.g.:  git push origin && git push --tags origin
diff --git a/doc/rteval.8 b/doc/rteval.8
index ba91751..fa24509 100644
--- a/doc/rteval.8
+++ b/doc/rteval.8
@@ -30,7 +30,7 @@
 realtime Linux kernel on a particular hardware system. There is
 nothing that actually requires a realtime Linux kernel, but the
 latency measurements will usually be pretty bad on a stock Linux
-kernel. 
+kernel.
 
 
 .SH OPTIONS
@@ -66,11 +66,17 @@
 .B \-w WORKDIR, \-\-workdir=WORKDIR
 Tell rteval to change directory to WORKDIR before creating any
 subdirectories for report files. The default WORKDIR is the directory
-in which rteval was started. 
+in which rteval was started.
 .TP
 .B \-l LOADDIR, \-\-loaddir=LOADDIR
 Tell rteval where to find the source for the loads
 .TP
+.B \-\-loads\-cpulist=CPULIST
+List of CPUs where loads will run
+.TP
+.B \-\-measurement-cpulist=CPULIST
+List of CPUs where measurement applciation will run
+.TP
 .B \-s, \-\-sysreport
 Have rteval run the sysreport utility after a run to gather
 information on the running system.
@@ -86,7 +92,7 @@
 .TP
 .B \-Z, \-\-summarize
 Have rteval summarize an existing report. This will not cause loads or
-meausurement utilities to be run. 
+meausurement utilities to be run.
 .TP
 .B \-H, \-\-raw-histogram
 Generate raw histogram data for an already existing XML report
@@ -133,14 +139,14 @@
 This is a directory created by rteval to hold the summary.xml file as
 well as other files collected on the system. It is the current Year,
 Month, and Day, followed by a sequence number for multiple runs on the
-same day. 
+same day.
 
 .BR summary.xml
 This is an XML file that contains information about a test run and the
 host system upon which rteval was run.
 .BR dmesg
 This is the output of the dmesg(1) program immediately following
-system boot. It is copied from /var/log/dmesg. 
+system boot. It is copied from /var/log/dmesg.
 
 .SH SEE ALSO
 .BR cyclictest(8).
diff --git a/doc/rteval.txt b/doc/rteval.txt
index 66b1320..7342866 100644
--- a/doc/rteval.txt
+++ b/doc/rteval.txt
@@ -22,7 +22,7 @@
 between event occurance and the servicing of that event (latency). 
 
 This paper describes the 'rteval' program, a Python 2.x program
-developed at Red Hat to help quantify realtime performance on the MRG
+developed at Red Hat to help quantify realtime performance on the
 Realtime kernel. Rteval is an attempt to put together a synthetic
 benchmark which mimics a well behaved realtime application, running on
 a heavily loaded realtime Linux system. It uses the 'cyclictest'
diff --git a/rteval-cmd b/rteval-cmd
new file mode 100755
index 0000000..ed61d98
--- /dev/null
+++ b/rteval-cmd
@@ -0,0 +1,322 @@
+#!/usr/bin/python -tt
+# -*- coding: utf-8 -*-
+#
+#   rteval - script for evaluating platform suitability for RT Linux
+#
+#           This program is used to determine the suitability of
+#           a system for use in a Real Time Linux environment.
+#           It starts up various system loads and measures event
+#           latency while the loads are running. A report is generated
+#           to show the latencies encountered during the run.
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.com>
+#   Copyright 2012 - 2013   Raphaël Beamonte <raphael.beamonte@gmail.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import sys, os, time, optparse, tempfile
+import libxml2, lxml.etree
+from datetime import datetime
+from rteval.Log import Log
+from rteval import RtEval, rtevalConfig
+from rteval.modules.loads import LoadModules
+from rteval.modules.measurement import MeasurementModules
+from rteval.version import RTEVAL_VERSION
+from rteval.misc import online_cpus, invert_cpulist, compress_cpulist
+
+def summarize(repfile, xslt):
+    isarchive = False
+    summaryfile = repfile
+    if repfile.endswith(".tar.bz2"):
+        import tarfile
+        try:
+            t = tarfile.open(repfile)
+        except:
+            print "Don't know how to summarize %s (tarfile open failed)" % repfile
+            return
+        element = None
+        for f in t.getnames():
+            if f.find('summary.xml') != -1:
+                element = f
+                break
+        if element == None:
+            print "No summary.xml found in tar archive %s" % repfile
+            return
+        tmp = tempfile.gettempdir()
+        t.extract(element, path=tmp)
+        summaryfile = os.path.join(tmp, element)
+        isarchive = True
+
+    # Load the XSLT template
+    xsltfp = open(xslt, "r")
+    xsltdoc = lxml.etree.parse(xsltfp)
+    xsltprs = lxml.etree.XSLT(xsltdoc)
+    xsltfp.close()
+
+    # Load the summay.xml report - with some simple sanity checks
+    xmlfp = open(summaryfile, "r")
+    xmldoc = lxml.etree.parse(xmlfp)
+    xmlfp.close()
+
+    if xmldoc.docinfo.root_name != 'rteval':
+        raise RuntimeError("The report doesn't seem like a rteval summary report")
+
+    # Parse and print the report through the XSLT template - preserve proper encoding
+    resdoc = xsltprs(xmldoc)
+    print unicode(resdoc).encode('UTF-8')
+
+    # Clean up
+    del resdoc
+    del xmldoc
+    del xsltprs
+    del xsltdoc
+
+    if isarchive:
+        os.unlink(summaryfile)
+
+
+
+def parse_options(cfg, parser, cmdargs):
+    '''parse the command line arguments'''
+
+    rtevcfg = cfg.GetSection('rteval')
+    #
+    # All the destination variables here should go into the 'rteval' section,
+    # thus they are prefixed with 'rteval___'.
+    # See rteval/rtevalConfig::UpdateFromOptionParser() method for more info
+    #
+    parser.add_option("-d", "--duration", dest="rteval___duration",
+                      type="string", default=rtevcfg.duration, metavar="DURATION",
+                      help="specify length of test run (default: %default)")
+    parser.add_option("-v", "--verbose", dest="rteval___verbose",
+                      action="store_true", default=rtevcfg.verbose,
+                      help="turn on verbose prints (default: %default)")
+    parser.add_option("-q", "--quiet", dest="rteval___quiet",
+                      action="store_true", default=rtevcfg.quiet,
+                      help="turn on quiet mode (default: %default)")
+    parser.add_option("-w", "--workdir", dest="rteval___workdir",
+                      type="string", default=rtevcfg.workdir, metavar="DIRECTORY",
+                      help="top directory for rteval data (default: %default)")
+    parser.add_option("-l", "--loaddir", dest="rteval___srcdir",
+                      type="string", default=rtevcfg.srcdir, metavar="DIRECTORY",
+                      help="directory for load source tarballs (default: %default)")
+    parser.add_option("-i", "--installdir", dest="rteval___installdir",
+                      type="string", default=rtevcfg.installdir, metavar="DIRECTORY",
+                      help="place to locate installed templates (default: %default)")
+    parser.add_option("-s", "--sysreport", dest="rteval___sysreport",
+                      action="store_true", default=rtevcfg.sysreport,
+                      help='run sysreport to collect system data (default: %default)')
+    parser.add_option("-D", '--debug', dest='rteval___debugging',
+                      action='store_true', default=rtevcfg.debugging,
+                      help='turn on debug prints (default: %default)')
+    parser.add_option("-X", '--xmlrpc-submit', dest='rteval___xmlrpc',
+                      action='store', default=rtevcfg.xmlrpc, metavar='HOST',
+                      help='Hostname to XML-RPC server to submit reports')
+    parser.add_option("-P", "--xmlrpc-no-abort", dest="rteval___xmlrpc_noabort",
+                      action='store_true', default=False,
+                      help="Do not abort if XML-RPC server do not respond to ping request");
+    parser.add_option("-Z", '--summarize', dest='rteval___summarize',
+                      action='store_true', default=False,
+                      help='summarize an already existing XML report')
+    parser.add_option("-H", '--raw-histogram', dest='rteval___rawhistogram',
+                      action='store_true', default=False,
+                      help='Generate raw histogram data for an already existing XML report')
+    parser.add_option("-f", "--inifile", dest="rteval___inifile",
+                      type='string', default=None, metavar="FILE",
+                      help="initialization file for configuring loads and behavior")
+    parser.add_option("-a", "--annotate", dest="rteval___annotate",
+                      type="string", default=None, metavar="STRING",
+                      help="Add a little annotation which is stored in the report")
+    parser.add_option("-L", "--logging", dest="rteval___logging",
+                      action='store_true', default=False,
+                      help='log the output of the loads in the report directory')
+    parser.add_option("-O", "--onlyload", dest="rteval___onlyload",
+                      action='store_true', default=False,
+                      help="only run the loads (don't run measurement threads)")
+    parser.add_option("-V", "--version", dest="rteval___version",
+                      action='store_true', default=False,
+                      help='print rteval version and exit')
+
+    if not cmdargs:
+        cmdargs = ["--help"]
+
+    (cmd_opts, cmd_args) = parser.parse_args(args = cmdargs)
+    if cmd_opts.rteval___version:
+        print("rteval version %s" % RTEVAL_VERSION)
+        sys.exit(0);
+
+    if cmd_opts.rteval___duration:
+        mult = 1.0
+        v = cmd_opts.rteval___duration.lower()
+        if v.endswith('s'):
+            v = v[:-1]
+        elif v.endswith('m'):
+            v = v[:-1]
+            mult = 60.0
+        elif v.endswith('h'):
+            v = v[:-1]
+            mult = 3600.0
+        elif v.endswith('d'):
+            v = v[:-1]
+            mult = 3600.0 * 24.0
+        cmd_opts.rteval___duration = float(v) * mult
+
+    # Update the config object with the parsed arguments
+    cfg.UpdateFromOptionParser(parser)
+
+    return cmd_args
+
+
+
+if __name__ == '__main__':
+    from rteval.sysinfo import dmi
+
+    dmi.ProcessWarnings()
+
+    # set LD_BIND_NOW to resolve shared library symbols
+    # note: any string will do, nothing significant about 'rteval'
+
+    os.environ['LD_BIND_NOW'] = 'rteval'
+
+    try:
+        # Prepare logging
+        logger = Log()
+        logger.SetLogVerbosity(Log.NONE)
+
+        # setup initial configuration
+        config = rtevalConfig.rtevalConfig(logger=logger)
+
+        # Before really parsing options, see if we have been given a config file in the args
+        # and load it - just so that default values are according to the config file
+        try:
+            cfgfile = sys.argv[sys.argv.index('-f')+1]
+            config.Load(cfgfile)
+        except IndexError:
+            # Missing file argument
+            raise RuntimeError('The -f option requires a file name to the configuration file')
+        except ValueError:
+            # No configuration file given, load defaults
+            config.Load()
+            pass
+
+        if not config.HasSection('loads'):
+            config.AppendConfig('loads',{
+                    'kcompile'   : 'module',
+                    'hackbench'  : 'module' })
+
+        if not config.HasSection('measurement'):
+            config.AppendConfig('measurement', {
+                    'cyclictest' : 'module',
+                    'sysstat' : 'module'})
+
+        # Prepare log levels before loading modules, not to have unwanted log messages
+        rtevcfg = config.GetSection('rteval')
+        if (sys.argv.count('-v')+sys.argv.count('--verbose')) > 0:
+            rtevcfg.verbose = True
+        if (sys.argv.count('-D')+sys.argv.count('--debug')) > 0:
+            rtevcfg.debugging = True
+        if (sys.argv.count('-q')+sys.argv.count('--quiet')) > 0:
+            rtevcfg.quiet = True
+        loglev = (not rtevcfg.quiet and (Log.ERR | Log.WARN)) \
+                | (rtevcfg.verbose and Log.INFO) \
+                | (rtevcfg.debugging and Log.DEBUG)
+        logger.SetLogVerbosity(loglev)
+
+        # Load modules
+        loadmods = LoadModules(config, logger=logger)
+        measuremods = MeasurementModules(config, logger=logger)
+
+        # parse command line options
+        parser = optparse.OptionParser()
+        loadmods.SetupModuleOptions(parser)
+        measuremods.SetupModuleOptions(parser)
+        cmd_args = parse_options(config, parser, sys.argv[1:])
+
+        # if we only specified one set of cpus (loads or measurement)
+        # default the other to the inverse of the specified list
+        ldcfg = config.GetSection('loads')
+        msrcfg = config.GetSection('measurement')
+        if not ldcfg.cpulist and msrcfg.cpulist:
+            ldcfg.cpulist = compress_cpulist(invert_cpulist(msrcfg.cpulist))
+        if not msrcfg.cpulist and ldcfg.cpulist:
+            msrcfg.cpulist = compress_cpulist(invert_cpulist(ldcfg.cpulist))
+
+        logger.log(Log.DEBUG, "workdir: %s" % rtevcfg.workdir)
+
+        # if --summarize was specified then just parse the XML, print it and exit
+        if rtevcfg.summarize or rtevcfg.rawhistogram:
+            if len(cmd_args) < 1:
+                raise RuntimeError, "Must specify at least one XML file with --summarize!"
+
+            for x in cmd_args:
+                if rtevcfg.summarize:
+                    summarize(x, '%s/rteval_text.xsl' % rtevcfg.installdir)
+                elif rtevcfg.rawhistogram:
+                    summarize(x, '%s/rteval_histogram_raw.xsl' % rtevcfg.installdir)
+
+            sys.exit(0)
+
+        if os.getuid() != 0:
+            print "Must be root to run rteval!"
+            sys.exit(-1)
+
+        logger.log(Log.DEBUG, '''rteval options:
+     workdir: %s
+     loaddir: %s
+     reportdir: %s
+     verbose: %s
+     debugging: %s
+     logging:  %s
+     duration: %f
+     sysreport: %s''' % (
+                rtevcfg.workdir, rtevcfg.srcdir,
+                rtevcfg.reportdir, rtevcfg.verbose,
+                rtevcfg.debugging, rtevcfg.logging,
+                rtevcfg.duration, rtevcfg.sysreport))
+
+        if not os.path.isdir(rtevcfg.workdir):
+            raise RuntimeError, "work directory %s does not exist" % rtevcfg.workdir
+
+
+        rteval = RtEval(config, loadmods, measuremods, logger)
+        rteval.Prepare(rtevcfg.onlyload)
+
+        if rtevcfg.onlyload:
+            # If --onlyload were given, just kick off the loads and nothing more
+            # No reports will be created.
+            loadmods.Start()
+            nthreads = loadmods.Unleash()
+            logger.log(Log.INFO, "Started %i load threads - will run for %f seconds" % (
+                    nthreads, rtevcfg.duration))
+            logger.log(Log.INFO, "No measurements will be performed, due to the --onlyload option")
+            time.sleep(rtevcfg.duration)
+            loadmods.Stop()
+            ec = 0
+        else:
+            # ... otherwise, run the full measurement suite with loads
+            ec = rteval.Measure()
+            logger.log(Log.DEBUG, "exiting with exit code: %d" % ec)
+
+        sys.exit(ec)
+    except KeyboardInterrupt:
+        sys.exit(0)
diff --git a/rteval-loads.spec b/rteval-loads.spec
index 5aebc56..96e4a59 100644
--- a/rteval-loads.spec
+++ b/rteval-loads.spec
@@ -1,14 +1,14 @@
 Name:		rteval-loads
-Version:	1.3
+Version:	1.4
 Release:	1%{?dist}
 Summary:	Source files for rteval loads
 Group:		Development/Tools
 License:	GPLv2
 URL:		http://git.kernel.org/?p=linux/kernel/git/clrkwllms/rteval.git
-Source0:	http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.39.tar.bz2
+Source0:	http://www.kernel.org/pub/linux/kernel/v4.9/linux-4.9.tar.xz
 
 BuildRoot:	%{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires:	gcc binutils make
+Requires:	gcc binutils make kernel-headers
 Obsoletes:	rteval-kcompile >= 1.0
 Obsoletes:	rteval-hackbench >= 1.0
 BuildArch:	noarch
@@ -37,6 +37,15 @@
 %doc
 
 %changelog
+* Tue Jan 10 2017 Clark Williams <williams@redhat.com> - 1.4-1
+- updated kernel tarball to 4.9 [1432625]
+
+* Fri Jun  5 2015 Clark Williams <williams@redhat.com> - 1.3-3
+- add requires for kernel-header package [1228740]
+
+* Mon Nov 10 2014 Luis Claudio R. Goncalves <lgoncalv@redhat.com> - 1.3-2
+-  rebuild for RHEL-7.1 (1151569)
+
 * Fri May 20 2011 Clark Williams <williams@redhat.com> - 1.3-1
 - updated kernel tarball to 2.6.39
 
@@ -50,7 +59,7 @@
 - removed hackbench tarball (now using rt-tests hackbench)
 
 * Fri Feb 19 2010 Clark Williams <williams@redhat.com> - 1.1-1
-- updated hackbench source with fixes from David Sommerseth 
+- updated hackbench source with fixes from David Sommerseth
   <davids@redhat.com> to cleanup child processes
 
 * Thu Nov  5 2009 Clark Williams <williams@redhat.com> - 1.0-1
diff --git a/rteval.conf b/rteval.conf
new file mode 100644
index 0000000..6b22b29
--- /dev/null
+++ b/rteval.conf
@@ -0,0 +1,13 @@
+#
+# default configuration file for rteval
+#
+[rteval]
+duration:  60.0
+report_interval: 600
+
+[measurement]
+cyclictest: module
+
+[loads]
+kcompile:  module
+hackbench: module
diff --git a/rteval.spec b/rteval.spec
index d92987d..d9f7d40 100644
--- a/rteval.spec
+++ b/rteval.spec
@@ -2,7 +2,7 @@
 %{!?python_ver: %define python_ver %(%{__python} -c "import sys ; print sys.version[:3]")}
 
 Name:		rteval
-Version:	1.41
+Version:	2.14
 Release:	1%{?dist}
 Summary:	Utility to evaluate system suitability for RT Linux
 
@@ -14,12 +14,18 @@
 
 BuildRequires:	python
 Requires:	python
-Requires:	python-schedutils python-ethtool libxslt-python >= 1.1.17
+Requires:	python-schedutils python-ethtool python-lxml
 Requires:	python-dmidecode >= 3.10
-Requires:	rt-tests >= 0.65
-Requires:	rteval-loads >= 1.2
+Requires:	rt-tests >= 0.97
+Requires:	rteval-loads >= 1.4
+Requires:	rteval-common => %{version}-%{release}
+Requires:	trace-cmd
+Requires:	sysstat
+Requires:	bzip2
+Requires:       kernel-headers
 BuildArch:	noarch
 Obsoletes:	rteval <= 1.7
+Requires:	numactl
 
 %description
 The rteval script is a utility for measuring various aspects of
@@ -30,70 +36,156 @@
 a statistical analysis of the event response times is done and printed
 to the screen.
 
+
+%package common
+Summary: Common rteval files
+BuildArch: noarch
+
+%description common
+Common files used by rteval, rteval-xmlrpc and rteval-parser
+
 %prep
 %setup -q
 
 # version sanity check (make sure specfile and rteval.py match)
-srcver=$(awk '/version =/ { print $3; }' rteval/rteval.py | sed -e 's/"\(.*\)"/\1/')
+cp rteval/version.py rtevalversion.py
+srcver=$(%{__python} -c "from rtevalversion import RTEVAL_VERSION; print RTEVAL_VERSION")
+rm -rf rtevalversion.py
 if [ $srcver != %{version} ]; then
    printf "\n***\n*** rteval spec file version do not match the rteval/rteval.py version\n***\n\n"
    exit -1
 fi
 
 %build
-
+%{__python} setup.py build
 
 %install
-rm -rf ${RPM_BUILD_ROOT}
-mkdir -p ${RPM_BUILD_ROOT}
-make DESTDIR=${RPM_BUILD_ROOT} install_rteval
-mkdir -p ${RPM_BUILD_ROOT}/usr/bin
-# note that python_sitelib has a leading slash...
-ln -s ../..%{python_sitelib}/rteval/rteval.py ${RPM_BUILD_ROOT}/usr/bin/rteval
-
+%{__python} setup.py install --root=$RPM_BUILD_ROOT
 
 %clean
 rm -rf $RPM_BUILD_ROOT
 
+%files common
+%doc COPYING
+%dir %{_datadir}/%{name}
+%{python_sitelib}/rteval/rtevalclient.py*
+%{python_sitelib}/rteval/rtevalConfig.py*
+%{python_sitelib}/rteval/rtevalXMLRPC.py*
+%{python_sitelib}/rteval/version.py*
+%{python_sitelib}/rteval/Log.py*
+%{python_sitelib}/rteval/misc.py*
+%{python_sitelib}/rteval/systopology.py*
+
 %files
 %defattr(-,root,root,-)
 %if "%{python_ver}" >= "2.5"
 %{python_sitelib}/*.egg-info
 %endif
 
-%dir %{_datadir}/%{name}
-
-%doc COPYING doc/rteval.txt
-%{_mandir}/man8/rteval.8*
-%{_datadir}/%{name}/rteval_*.xsl
+%doc COPYING README doc/rteval.txt
+%{_mandir}/man8/rteval.8.gz
 %config(noreplace) %{_sysconfdir}/rteval.conf
-%{python_sitelib}/rteval/
+%{_datadir}/%{name}/rteval_*.xsl
+%{python_sitelib}/rteval/__init__.py*
+%{python_sitelib}/rteval/rtevalMailer.py*
+%{python_sitelib}/rteval/rtevalReport.py*
+%{python_sitelib}/rteval/xmlout.py*
+%{python_sitelib}/rteval/modules
+%{python_sitelib}/rteval/sysinfo
 /usr/bin/rteval
 
 %changelog
-* Mon Nov 23 2015 Clark Williams <williams@redhat.com> - 1.41-1
-- hackbench: fix naming error in logging filehandles
+* Thu Mar 16 2017 Clark Williams <williams@redhat.com> - 2.14-1
+- removed leftover import of systopology from sysinfo
 
-* Mon Nov 23 2015 Clark Williams <williams@redhat.com> - 1.40-2
-- fix version mismatch in spec, setup and rteval
+* Wed Mar 15 2017 Clark Williams <williams@redhat.com> - 2.13-2
+- Updated specfile to correct version and bz [1382155]
 
-* Fri Nov 20 2015 Clark Williams <williams@redhat.com> - 1.40-1
-- hackbench: modify to avoid cross-node NUMA copies
+* Tue Sep 20 2016 Clark Williams <williams@rehdat.com> - 2.12-1
+- handle empty environment variables SUDO_USER and USER [1312057]
 
-* Thu Jul  9 2015 Clark Williams <williams@redhat.com> - 1.39-2
-- fixed up specfile damage from merge
+* Tue Aug 30 2016 Clark Williams <williams@rehdat.com> - 2.11-1
+- make sure we return non-zero for early exit from tests
 
-* Mon Jun 29 2015 Clark Williams <williams@redhat.com> - 1.39-1
-- merged David Sommerseth devel branch
+* Wed Aug  3 2016 Clark Williams <williams@rehdat.com> - 2.10-1
+- bumped version for RHEL 7.3 release
 
-* Tue Sep 17 2013 Clark Williams <williams@redhat.com> - 1.38-1
-- cleaned up incorrect usage of percent signs in changelog
-- added data validation checks to histogram parsing code
+* Mon May  9 2016 Clark Williams <williams@redhat.com> - 2.9.1
+- default cpulist for modules if only one specified [1333831]
 
-* Thu Dec 13 2012 Clark Williams <williams@redhat.com> - 1.37-1
-- added module specific command line options
-- From Raphaël Beamonte <raphael.beamonte@gmail.com>:
-  - Change getcmdpath method to use only python calls to find paths
+* Tue Apr 26 2016 Clark Williams <williams@redhat.com> - 2.8.1
+- add the --version option to print the rteval version
+- made the --cyclictest-breaktrace option work properly [1209986]
+
+* Fri Apr  1 2016 Clark Williams <williams@redhat.com> - 2.7.1
+- treat SIGINT and SIGTERM as valid end-of-run events [1278757]
+- added cpulist options to man page
+
+* Thu Feb 11 2016 Clark Williams <williams@redhat.com> - 2.6.1
+- update to make --loads-cpulist and --measurement-cpulist work [1306437]
+
+* Thu Dec 10 2015 Clark Williams <williams@refhat.com> - 2.5-1
+- stop using old numactl --cpubind argument
+
+* Wed Dec  9 2015 Clark Williams <williams@refhat.com> - 2.4.2
+- added Require of package numactl
+
+* Tue Nov 17 2015 Clark Williams <williams@refhat.com> - 2.4.1
+- rework hackbench load to not generate cross-node traffic [1282826]
+
+* Wed Aug 12 2015 Clark Williams <williams@redhat.com> - 2.3-1
+- comment out HWLatDetect module from default config [1245699]
+
+* Wed Jun 10 2015 Clark Williams <williams@redhat.com> - 2.2-1
+- add --loads-cpulist and --measurement-cpulist to allow cpu placement [1230401]
+
+* Thu Apr 23 2015 Luis Claudio R. Goncalves <lgoncalv@redhat.com> - 2.1-8
+- load default configs when no config file is specified (Jiri kastner) [1212452]
+
+* Wed Jan 14 2015 Clark Williams <williams@redhat.com> - 2.1-7
+- added requires of bzip2 to specfile [1151567]
+
+* Thu Jan  8 2015 Clark Williams <williams@redhat.com> - 2.1-6
+- cleaned up product documentation [1173315]
+
+* Mon Nov 10 2014 Luis Claudio R. Goncalves <lgoncalv@redhat.com> - 2.1-5
+- rebuild for RHEL-7.1 (1151567)
+
+* Thu Mar 27 2014 Clark Williams <williams@redhat.com> - 2.1-4
+- cherry-picked old commit to deal with installdir problem
+
+* Wed Mar 26 2014 Clark Williams <williams@redhat.com> - 2.1-3
+- added sysstat requires to specfile
+
+* Tue Mar 12 2013 David Sommerseth <davids@redhat.com> - 2.1-2
+- Migrated from libxslt-python to python-lxml
+
+* Fri Jan 18 2013 David Sommerseth <davids@redhat.com> - 2.1-1
+- Made some log lines clearer
+- cyclictest: Added --cyclictest-breaktrace feature
+- cyclictest: Removed --cyclictest-distance option
+- cyclictest: Use a tempfile buffer for cyclictest's stdout data
+- cyclictest: Report if breaktrace was triggered
+- cyclictest: Make the unit test work again
+- cyclictest: Only log and show statistic data when samples are collected
+- Copyright updates
+
+* Thu Jan 17 2013 David Sommerseth <davids@redhat.com> - 2.0.1-1
+- Fix up type casting in the core module code
+- hwlatdetect: Add some more useful debug info
+- Reworked the run logic for modules - allow them to flag they won't run
+- Fixed a few log messages in load modules
+- Add a 30 seconds sleep before unleashing the measurement threads
+
+* Thu Jan 10 2013 David Sommerseth <davids@redhat.com> - 2.0-3
+- Separate out RTEVAL_VERSION into rteval.version, to avoid
+  massive BuildRequirements
+
+* Fri Dec 21 2012 David Sommerseth <davids@redhat.com> - 2.0-2
+- Split out common files into rteval-common
+
+* Fri Dec 21 2012 David Sommerseth <davids@redhat.com> - 2.0-1
+- Updated to rteval v2.0 and reworked spec file to use setup.py directly
 
 * Tue Oct 23 2012 Clark Williams <williams@redhat.com> - 1.36-1
 - deal with system not having dmidecode python module
diff --git a/rteval/Log.py b/rteval/Log.py
new file mode 100644
index 0000000..a41ef31
--- /dev/null
+++ b/rteval/Log.py
@@ -0,0 +1,109 @@
+#
+#   Copyright 2012 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import sys
+
+class Log(object):
+    NONE   = 0
+    ALWAYS = 0
+    INFO   = 1<<0
+    WARN   = 1<<1
+    ERR    = 1<<2
+    DEBUG  = 1<<3
+
+
+    def __init__(self, logfile=None):
+        if logfile is not None:
+            self.__logfile = open(logfile, "w")
+        else:
+            self.__logfile = sys.stdout
+        self.__logverb = self.INFO
+
+
+    def __logtype_str(self, ltype):
+        if ltype == self.ALWAYS:
+            return ""
+        if ltype == self.INFO:
+            return "[INFO] "
+        elif ltype == self.WARN:
+            return "[WARNING] "
+        elif ltype == self.ERR:
+            return "[ERROR] "
+        elif ltype == self.DEBUG:
+            return "[DEBUG] "
+
+
+    def SetLogVerbosity(self, logverb):
+        self.__logverb = logverb
+
+
+    def log(self, logtype, msg):
+        if (logtype & self.__logverb) or logtype == self.ALWAYS:
+            self.__logfile.write("%s%s\n" %
+                                 (self.__logtype_str(logtype),
+                                  msg)
+                                 )
+
+
+
+def unit_test(rootdir):
+    from itertools import takewhile, count
+
+    logtypes = (Log.ALWAYS, Log.INFO, Log.WARN, Log.ERR, Log.DEBUG)
+    logtypes_s = ("ALWAYS", "INFO", "WARN", "ERR", "DEBUG")
+
+    def test_log(l, msg):
+        for lt in logtypes:
+            l.log(lt, msg)
+
+    def run_log_test(l):
+        for lt in range(min(logtypes), max(logtypes)*2):
+            test = ", ".join([logtypes_s[logtypes.index(i)] for i in [p for p in takewhile(lambda x: x <= lt, (2**i for i in count())) if p & lt]])
+            print "Testing verbosity flags set to: (%i) %s" % (lt, test)
+            msg = "Log entry when verbosity is set to %i [%s]" % (lt, test)
+            l.SetLogVerbosity(lt)
+            test_log(l, msg)
+            print "-"*20
+
+    try:
+        print "** Testing stdout"
+        l = Log()
+        run_log_test(l)
+
+        print "** Testing file logging - using test.log"
+        l = Log("test.log")
+        run_log_test(l)
+
+        return 0
+    except Exception, e:
+        import traceback
+        traceback.print_exc(file=sys.stdout)
+        print "** EXCEPTION %s", str(e)
+        return 1
+
+
+
+if __name__ == '__main__':
+    unit_test(None)
+
diff --git a/rteval/__init__.py b/rteval/__init__.py
index e1c8289..a7e44d9 100644
--- a/rteval/__init__.py
+++ b/rteval/__init__.py
@@ -1,7 +1,283 @@
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
 """
-Copyright (c) 2008,2009,2010  Red Hat Inc.
+Copyright (c) 2008-2016  Red Hat Inc.
 
 Realtime verification utility
 """
-__author__ = "Clark Williams <williams@redhat.com>"
+__author__ = "Clark Williams <williams@redhat.com>, David Sommerseth <davids@redhat.com>"
 __license__ = "GPLv2 License"
+
+import os, signal, sys, threading, time
+from datetime import datetime
+from distutils import sysconfig
+from modules.loads import LoadModules
+from modules.measurement import MeasurementModules, MeasurementProfile
+from rtevalReport import rtevalReport
+from rtevalXMLRPC import rtevalXMLRPC
+from Log import Log
+import rtevalConfig, rtevalMailer
+import version
+
+RTEVAL_VERSION = version.RTEVAL_VERSION
+
+earlystop = False
+
+stopsig_received = False
+def sig_handler(signum, frame):
+
+    if signum == signal.SIGINT or signum == signal.SIGTERM:
+        global stopsig_received
+        stopsig_received = True
+        print "*** stop signal received - stopping rteval run ***"
+    else:
+        raise RuntimeError("SIGNAL received! (%d)" % signum)
+
+class RtEval(rtevalReport):
+    def __init__(self, config, loadmods, measuremods, logger):
+        self.__version = RTEVAL_VERSION
+
+        if not isinstance(config, rtevalConfig.rtevalConfig):
+            raise TypeError("config variable is not an rtevalConfig object")
+
+        if not isinstance(loadmods, LoadModules):
+            raise TypeError("loadmods variable is not a LoadModules object")
+
+        if not isinstance(measuremods, MeasurementModules):
+            raise TypeError("measuremods variable is not a MeasurementModules object")
+
+        if not isinstance(logger, Log):
+            raise TypeError("logger variable is not an Log object")
+
+        self.__cfg = config
+        self.__logger = logger
+        self._loadmods = loadmods
+        self._measuremods = measuremods
+
+        self.__rtevcfg = self.__cfg.GetSection('rteval')
+        self.__reportdir = None
+
+        # Import SystemInfo here, to avoid DMI warnings if RtEval() is not used
+        from sysinfo import SystemInfo
+        self._sysinfo = SystemInfo(self.__rtevcfg, logger=self.__logger)
+
+        # prepare a mailer, if that's configured
+        if self.__cfg.HasSection('smtp'):
+            self.__mailer = rtevalMailer.rtevalMailer(self.__cfg.GetSection('smtp'))
+        else:
+            self.__mailer = None
+
+        if not os.path.exists(self.__rtevcfg.xslt_report):
+            raise RuntimeError("can't find XSL template (%s)!" % self.__rtevcfg.xslt_report)
+
+        # Add rteval directory into module search path
+        sys.path.insert(0, '%s/rteval' % sysconfig.get_python_lib())
+
+        # Initialise the report module
+        rtevalReport.__init__(self, self.__version,
+                              self.__rtevcfg.installdir, self.__rtevcfg.annotate)
+
+        # If --xmlrpc-submit is given, check that we can access the server
+        if self.__rtevcfg.xmlrpc:
+            self.__xmlrpc = rtevalXMLRPC(self.__rtevcfg.xmlrpc, self.__logger, self.__mailer)
+            if not self.__xmlrpc.Ping():
+                if not self.__rtevcfg.xmlrpc_noabort:
+                    print "ERROR: Could not reach XML-RPC server '%s'.  Aborting." % \
+                        self.__rtevcfg.xmlrpc
+                    sys.exit(2)
+                else:
+                    print "WARNING: Could not ping the XML-RPC server.  Will continue anyway."
+        else:
+            self.__xmlrpc = None
+
+
+    def __show_remaining_time(self, remaining):
+        r = int(remaining)
+        days = r / 86400
+        if days: r = r - (days * 86400)
+        hours = r / 3600
+        if hours: r = r - (hours * 3600)
+        minutes = r / 60
+        if minutes: r = r - (minutes * 60)
+        print "rteval time remaining: %d days, %d hours, %d minutes, %d seconds" % (days, hours, minutes, r)
+
+
+    def Prepare(self, onlyload = False):
+        builddir = os.path.join(self.__rtevcfg.workdir, 'rteval-build')
+        if not os.path.isdir(builddir): os.mkdir(builddir)
+
+        # create our report directory
+        try:
+            # Only create a report dir if we're doing measurements
+            # or the loads logging is enabled
+            if not onlyload or self.__rtevcfg.logging:
+                self.__reportdir = self._make_report_dir(self.__rtevcfg.workdir, "summary.xml")
+        except Exception, e:
+            raise RuntimeError("Cannot create report directory (NFS with rootsquash on?) [%s]", str(e))
+
+        self.__logger.log(Log.INFO, "Preparing load modules")
+        params = {'workdir':self.__rtevcfg.workdir,
+                  'reportdir':self.__reportdir and self.__reportdir or "",
+                  'builddir':builddir,
+                  'srcdir':self.__rtevcfg.srcdir,
+                  'verbose': self.__rtevcfg.verbose,
+                  'debugging': self.__rtevcfg.debugging,
+                  'numcores':self._sysinfo.cpu_getCores(True),
+                  'logging':self.__rtevcfg.logging,
+                  'memsize':self._sysinfo.mem_get_size(),
+                  'numanodes':self._sysinfo.mem_get_numa_nodes(),
+                  'duration': float(self.__rtevcfg.duration),
+                  }
+        self._loadmods.Setup(params)
+
+        self.__logger.log(Log.INFO, "Preparing measurement modules")
+        self._measuremods.Setup(params)
+
+
+    def __RunMeasurementProfile(self, measure_profile):
+        global earlystop
+        if not isinstance(measure_profile, MeasurementProfile):
+            raise Exception("measure_profile is not an MeasurementProfile object")
+
+        measure_start = None
+        (with_loads, run_parallel) = measure_profile.GetProfile()
+        self.__logger.log(Log.INFO, "Using measurement profile [loads: %s  parallel: %s]" % (
+                with_loads, run_parallel))
+        try:
+            nthreads = 0
+
+            # start the loads
+            if with_loads:
+                self._loadmods.Start()
+
+            print "rteval run on %s started at %s" % (os.uname()[2], time.asctime())
+            onlinecpus = self._sysinfo.cpu_getCores(True)
+            cpulist = self._loadmods._cfg.GetSection("loads").cpulist
+            if cpulist:
+                print "started %d loads on cores %s" % (self._loadmods.ModulesLoaded(), cpulist),
+            else:
+                print "started %d loads on %d cores" % (self._loadmods.ModulesLoaded(), onlinecpus),
+            if self._sysinfo.mem_get_numa_nodes() > 1:
+                print " with %d numa nodes" % self._sysinfo.mem_get_numa_nodes()
+            else:
+                print ""
+            cpulist = self._measuremods._MeasurementModules__cfg.GetSection("measurement").cpulist
+            if cpulist:
+                print "started measurement threads on cores %s" % cpulist
+            else:
+                print "started measurement threads on %d cores" % onlinecpus
+            print "Run duration: %s seconds" % str(self.__rtevcfg.duration)
+
+            # start the cyclictest thread
+            measure_profile.Start()
+
+            # Unleash the loads and measurement threads
+            report_interval = int(self.__rtevcfg.report_interval)
+            nthreads = with_loads and self._loadmods.Unleash() or None
+            self.__logger.log(Log.INFO, "Waiting 30 seconds to let load modules settle down")
+            time.sleep(30)
+            measure_profile.Unleash()
+            measure_start = datetime.now()
+
+            # wait for time to expire or thread to die
+            signal.signal(signal.SIGINT, sig_handler)
+            signal.signal(signal.SIGTERM, sig_handler)
+            self.__logger.log(Log.INFO, "waiting for duration (%s)" % str(self.__rtevcfg.duration))
+            stoptime = (time.time() + float(self.__rtevcfg.duration))
+            currtime = time.time()
+            rpttime = currtime + report_interval
+            load_avg_checked = 5
+            while (currtime <= stoptime) and not stopsig_received:
+                time.sleep(60.0)
+                if not measure_profile.isAlive():
+                    stoptime = currtime
+                    earlystop = True
+                    self.__logger.log(Log.WARN,
+                                      "Measurement threads did not use the full time slot. Doing a controlled stop.")
+
+                if with_loads:
+                    if len(threading.enumerate()) < nthreads:
+                        raise RuntimeError, "load thread died!"
+
+                if not load_avg_checked:
+                    self._loadmods.SaveLoadAvg()
+                    load_avg_checked = 5
+                else:
+                    load_avg_checked -= 1
+
+                if currtime >= rpttime:
+                    left_to_run = stoptime - currtime
+                    self.__show_remaining_time(left_to_run)
+                    rpttime = currtime + report_interval
+                    print "load average: %.2f" % self._loadmods.GetLoadAvg()
+                currtime = time.time()
+
+            self.__logger.log(Log.DEBUG, "out of measurement loop")
+            signal.signal(signal.SIGINT, signal.SIG_DFL)
+            signal.signal(signal.SIGTERM, signal.SIG_DFL)
+
+        except RuntimeError, e:
+            raise RuntimeError("appeared during measurement: %s" % e)
+
+        finally:
+            # stop measurement threads
+            measure_profile.Stop()
+
+            # stop the loads
+            if with_loads:
+                self._loadmods.Stop()
+
+        print "stopping run at %s" % time.asctime()
+
+        # wait for measurement modules to finish calculating stats
+        measure_profile.WaitForCompletion()
+
+        return measure_start
+
+
+    def Measure(self):
+        global earlystop
+        # Run the full measurement suite with reports
+        rtevalres = 0
+        measure_start = None
+        for meas_prf in self._measuremods:
+            mstart = self.__RunMeasurementProfile(meas_prf)
+            if measure_start is None:
+                measure_start = mstart
+
+        self._report(measure_start, self.__rtevcfg.xslt_report)
+        if self.__rtevcfg.sysreport:
+            self._sysinfo.run_sysreport(self.__reportdir)
+
+        # if --xmlrpc-submit | -X was given, send our report to the given host
+        if self.__xmlrpc:
+            retvalres = self.__xmlrpc.SendReport(self.GetXMLreport())
+
+        if earlystop:
+            rtevalres = 1
+        self._sysinfo.copy_dmesg(self.__reportdir)
+        self._tar_results()
+        return rtevalres
diff --git a/rteval/dmi.py b/rteval/dmi.py
deleted file mode 100644
index d317c3c..0000000
--- a/rteval/dmi.py
+++ /dev/null
@@ -1,134 +0,0 @@
-#
-#   dmi.py - class to wrap DMI Table information
-#
-#   Copyright 2009,2010   Clark Williams <williams@redhat.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
-#
-#   For the avoidance of doubt the "preferred form" of this code is one which
-#   is in an open unpatent encumbered format. Where cryptographic key signing
-#   forms part of the process of creating an executable the information
-#   including keys needed to generate an equivalently functional executable
-#   are deemed to be part of the source code.
-#
-
-import sys
-import os
-import subprocess
-sys.pathconf = "."
-import xmlout
-import libxml2
-import libxslt
-
-try:
-    import dmidecode
-except:
-    class dmidecode(object):
-        fake = 1
-        def __init__(self):
-            pass
-        
-
-def ProcessWarnings():
-    
-    if hasattr(dmidecode, "fake") or not hasattr(dmidecode, 'get_warnings'):
-        return
-
-    warnings = dmidecode.get_warnings()
-    if warnings == None:
-        return
-
-    for warnline in warnings.split('\n'):
-        # Ignore these warnings, as they are "valid" if not running as root
-        if warnline == '/dev/mem: Permission denied':
-            continue
-        if warnline == 'No SMBIOS nor DMI entry point found, sorry.':
-            continue
-
-        # All other warnings will be printed
-        if len(warnline) > 0:
-            print "** DMI WARNING ** %s" % warnline
-
-    dmidecode.clear_warnings()
-
-
-class DMIinfo(object):
-    '''class used to obtain DMI info via python-dmidecode'''
-
-    def __init__(self, config):
-        self.version = '0.3'
-        self.smbios = None
-        self.sharedir = config.installdir
-
-        if hasattr(dmidecode, "fake"):
-            return
-
-        self.dmixml = dmidecode.dmidecodeXML()
-        self.smbios = dmidecode.dmi.replace('SMBIOS ', '').replace(' present', '')
-
-        xsltdoc = self.__load_xslt('rteval_dmi.xsl')
-        self.xsltparser = libxslt.parseStylesheetDoc(xsltdoc)
-
-
-    def __load_xslt(self, fname):
-        if os.path.exists(fname):
-            return libxml2.parseFile(fname)
-        elif os.path.exists(self.sharedir + '/' + fname):
-            return libxml2.parseFile(self.sharedir + '/' + fname)
-        else:
-            raise RuntimeError, 'Could not locate XSLT template for DMI data (%s)' % fname
-
-    def genxml(self, xml):
-        if hasattr(dmidecode, "fake"):
-            return
-        self.dmixml.SetResultType(dmidecode.DMIXML_DOC)
-        resdoc = self.xsltparser.applyStylesheet(self.dmixml.QuerySection('all'), None)
-        node = resdoc.getRootElement().copyNode(1)
-        node.newProp("DMIinfo_version", self.version)
-        xml.AppendXMLnodes(node)
-
-
-def unit_test(rootdir):
-    from pprint import pprint
-
-    class unittest_ConfigDummy(object):
-        def __init__(self, rootdir):
-            self.config = {'installdir': '%s/rteval'}
-            self.__update_vars()
-
-        def __update_vars(self):
-            for k in self.config.keys():
-                self.__dict__[k] = self.config[k]
-
-    try:
-        ProcessWarnings()
-        if os.getuid() != 0:
-            print "** ERROR **  Must be root to run this unit_test()"
-            return 1
-
-        cfg = unittest_ConfigDummy(rootdir)
-        d = DMIinfo(cfg)
-        x = xmlout.XMLOut('dmi_test', "0.0")
-        x.NewReport()
-        d.genxml(x)
-        x.close()
-        x.Write('-')
-        return 0
-    except Exception, e:
-        print "** EXCEPTION: %s" % str(e)
-        return 1
-
-if __name__ == '__main__':
-    sys.exit(unit_test('.'))
diff --git a/rteval/kcompile.py b/rteval/kcompile.py
deleted file mode 100644
index e4c9034..0000000
--- a/rteval/kcompile.py
+++ /dev/null
@@ -1,159 +0,0 @@
-#
-#   Copyright 2009,2010   Clark Williams <williams@redhat.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
-#
-#   For the avoidance of doubt the "preferred form" of this code is one which
-#   is in an open unpatent encumbered format. Where cryptographic key signing
-#   forms part of the process of creating an executable the information
-#   including keys needed to generate an equivalently functional executable
-#   are deemed to be part of the source code.
-#
-import sys
-import os
-import time
-import glob
-import subprocess
-from signal import SIGTERM
-sys.pathconf = "."
-import load
-import xmlout
-
-kernel_prefix="linux-2.6"
-
-class Kcompile(load.Load):
-    def __init__(self, params={}):
-        load.Load.__init__(self, "kcompile", params)
-
-    def setup(self):
-        # find our source tarball
-        if self.params.has_key('tarball'):
-            tarfile = os.path.join(self.srcdir, self.params.tarfile)
-            if not os.path.exists(tarfile):
-                raise RuntimeError, " tarfile %s does not exist!" % tarfile
-            self.source = tarfile
-        else:
-            tarfiles = glob.glob(os.path.join(self.srcdir, "%s*" % kernel_prefix))
-            if len(tarfiles):
-                self.source = tarfiles[0]
-            else:
-                raise RuntimeError, " no kernel tarballs found in %s" % self.srcdir
-
-        # check for existing directory
-        kdir=None
-        names=os.listdir(self.builddir)
-        for d in names:
-            if d.startswith(kernel_prefix):
-                kdir=d
-                break
-        if kdir == None:
-            self.debug("unpacking kernel tarball")
-            tarargs = ['tar', '-C', self.builddir, '-x']
-            if self.source.endswith(".bz2"):
-                tarargs.append("-j")
-            elif self.source.endswith(".gz"):
-                tarargs.append("-z")
-            tarargs.append("-f")
-            tarargs.append(self.source)
-            try:
-                subprocess.call(tarargs)
-            except:
-                self.debug("untar'ing kernel self.source failed!")
-                sys.exit(-1)
-            names = os.listdir(self.builddir)
-            for d in names:
-                self.debug("checking %s" % d)
-                if d.startswith(kernel_prefix):
-                    kdir=d
-                    break
-        if kdir == None:
-            raise RuntimeError, "Can't find kernel directory!"
-        self.mydir = os.path.join(self.builddir, kdir)
-        self.debug("mydir = %s" % self.mydir)
-
-    def build(self):
-        self.debug("setting up all module config file in %s" % self.mydir)
-        null = os.open("/dev/null", os.O_RDWR)
-        out = self.open_logfile("kcompile-build.stdout")
-        err = self.open_logfile("kcompile-build.stderr")
-        # clean up from potential previous run
-        try:
-            ret = subprocess.call(["make", "-C", self.mydir, "mrproper", "allmodconfig"], 
-                                  stdin=null, stdout=out, stderr=err)
-            if ret:
-                raise RuntimeError, "kcompile setup failed: %d" % ret
-        except KeyboardInterrupt, m:
-            self.debug("keyboard interrupt, aborting")
-            return
-        self.debug("ready to run")
-        self.ready = True
-        os.close(null)
-        os.close(out)
-        os.close(err)
-
-    def calc_numjobs(self):
-        mult = int(self.params.setdefault('jobspercore', 1))
-        mem = self.memsize[0]
-        if self.memsize[1] == 'KB':
-            mem = mem / (1024.0 * 1024.0)
-        elif self.memsize[1] == 'MB':
-            mem = mem / 1024.0
-        elif self.memsize[1] == 'TB':
-            mem = mem * 1024
-        ratio = float(mem) / float(self.num_cpus)
-        if ratio > 1.0:
-            njobs = self.num_cpus * mult
-        else:
-            self.debug("low memory system (%f GB/core)! Dropping jobs to one per core\n" % ratio)
-            njobs = self.num_cpus
-        return njobs
-
-    def runload(self):
-        null = os.open("/dev/null", os.O_RDWR)
-        if self.logging:
-            out = self.open_logfile("kcompile.stdout")
-            err = self.open_logfile("kcompile.stderr")
-        else:
-            out = err = null
-
-        njobs = self.calc_numjobs()
-        self.debug("starting loop (jobs: %d)" % njobs)
-        self.args = ["make", "-C", self.mydir, 
-                     "-j%d" % njobs ] 
-        p = subprocess.Popen(self.args, 
-                             stdin=null,stdout=out,stderr=err)
-        while not self.stopevent.isSet():
-            time.sleep(1.0)
-            if p.poll() != None:
-                r = p.wait()
-                self.debug("restarting compile job (exit status: %s)" % r)
-                p = subprocess.Popen(self.args,
-                                     stdin=null,stdout=out,stderr=err)
-        self.debug("out of stopevent loop")
-        if p.poll() == None:
-            self.debug("killing compile job with SIGTERM")
-            os.kill(p.pid, SIGTERM)
-        p.wait()
-        os.close(null)
-        if self.logging:
-            os.close(out)
-            os.close(err)
-
-    def genxml(self, x):
-        x.taggedvalue('command_line', ' '.join(self.args), {'name':'kcompile', 'run':'1'})
-
-def create(params = {}):
-    return Kcompile(params)
-    
diff --git a/rteval/load.py b/rteval/load.py
deleted file mode 100644
index 5d76999..0000000
--- a/rteval/load.py
+++ /dev/null
@@ -1,96 +0,0 @@
-#
-#   Copyright 2009,2010   Clark Williams <williams@redhat.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
-#
-#   For the avoidance of doubt the "preferred form" of this code is one which
-#   is in an open unpatent encumbered format. Where cryptographic key signing
-#   forms part of the process of creating an executable the information
-#   including keys needed to generate an equivalently functional executable
-#   are deemed to be part of the source code.
-#
-
-import sys
-import os
-import os.path
-import time
-import subprocess
-import threading
-
-class Load(threading.Thread):
-    def __init__(self, name="<unnamed>", params={}):
-        threading.Thread.__init__(self)
-        self.name = name
-        self.builddir = params.setdefault('builddir', os.path.abspath("../build"))	# abs path to top dir
-        self.srcdir = params.setdefault('srcdir', os.path.abspath("../loadsource"))	# abs path to src dir
-        self.num_cpus = params.setdefault('numcores', 1)
-        self.debugging = params.setdefault('debugging', False)
-        self.source = params.setdefault('source', None)
-        self.reportdir = params.setdefault('reportdir', os.getcwd())
-        self.logging = params.setdefault('logging', False)
-        self.memsize = params.setdefault('memsize', (0, 'GB'))
-        self.params = params
-        self.ready = False
-        self.mydir = None
-        self.startevent = threading.Event()
-        self.stopevent = threading.Event()
-
-        if not os.path.exists(self.builddir):
-            os.makedirs(self.builddir)
-
-    def debug(self, str):
-        if self.debugging: print "%s: %s" % (self.name, str)
-
-    def isReady(self):
-        return self.ready
-
-    def shouldStop(self):
-        return self.stopevent.isSet()
-
-    def shouldStart(self):
-        return self.startevent.isSet()
-
-    def setup(self, builddir, tarball):
-        pass
-
-    def build(self, builddir):
-        pass
-
-    def runload(self, rundir):
-        pass
-
-    def run(self):
-        if self.shouldStop():
-            return
-        self.setup()
-        if self.shouldStop():
-            return
-        self.build()
-        while True:
-            if self.shouldStop():
-                return
-            self.startevent.wait(1.0)
-            if self.shouldStart():
-                break
-        self.runload()
-
-    def report(self):
-        pass
-
-    def genxml(self, x):
-        pass
-
-    def open_logfile(self, name):
-        return os.open(os.path.join(self.reportdir, "logs", name), os.O_CREAT|os.O_WRONLY)
diff --git a/rteval/misc.py b/rteval/misc.py
new file mode 100644
index 0000000..7b55b34
--- /dev/null
+++ b/rteval/misc.py
@@ -0,0 +1,69 @@
+#!/usr/bin/python -tt
+#
+# Copyright (C) 2015 Clark Williams <clark.williams@gmail.com>
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+
+import os
+import glob
+
+# expand a string range into a list
+# don't error check against online cpus
+def expand_cpulist(cpulist):
+    '''expand a range string into an array of cpu numbers'''
+    result = []
+    for part in cpulist.split(','):
+        if '-' in part:
+            a, b = part.split('-')
+            a, b = int(a), int(b)
+            result.extend(range(a, b + 1))
+        else:
+            a = int(part)
+            result.append(a)
+    return [ str(i) for i in list(set(result)) ]
+
+def online_cpus():
+    return [ str(c.replace('/sys/devices/system/cpu/cpu', ''))  for c in glob.glob('/sys/devices/system/cpu/cpu[0-9]*') ]
+
+def invert_cpulist(cpulist):
+    return [ c for c in online_cpus() if c not in cpulist]
+
+def compress_cpulist(cpulist):
+    return ",".join(cpulist)
+
+def cpuinfo():
+    core = -1
+    info = {}
+    for l in open('/proc/cpuinfo'):
+        l = l.strip()
+        if not l: continue
+        key,val = [ i.strip() for i in l.split(':')]
+        if key == 'processor':
+            core = val
+            info[core] = {}
+            continue
+        info[core][key] = val
+    return info
+
+if __name__ == "__main__":
+
+    info = cpuinfo()
+    idx = info.keys()
+    idx.sort()
+    for i in idx:
+        print "%s: %s" % (i, info[i])
+
+    print "0: %s" % (info['0']['model name'])
diff --git a/rteval/modules/__init__.py b/rteval/modules/__init__.py
new file mode 100644
index 0000000..b41653e
--- /dev/null
+++ b/rteval/modules/__init__.py
@@ -0,0 +1,545 @@
+#
+#   Copyright 2012 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+from rteval.Log import Log
+from rteval.rtevalConfig import rtevalCfgSection
+from datetime import datetime
+import time, libxml2, threading, optparse
+
+__all__ = ["rtevalRuntimeError", "rtevalModulePrototype", "ModuleContainer", "RtEvalModules"]
+
+class rtevalRuntimeError(RuntimeError):
+    def __init__(self, mod, message):
+        RuntimeError.__init__(self, message)
+
+        # The module had a RuntimeError, we set the flag
+        mod._setRuntimeError()
+
+
+class rtevalModulePrototype(threading.Thread):
+    "Prototype class for rteval modules - must be inherited by the real module"
+
+    def __init__(self, modtype, name, logger=None):
+        if logger and not isinstance(logger, Log):
+            raise TypeError("logger attribute is not a Log() object")
+
+        threading.Thread.__init__(self)
+
+        self._module_type = modtype
+        self._name = name
+        self.__logger = logger
+        self.__ready = False
+        self.__runtimeError = False
+        self.__events = {"start": threading.Event(),
+                         "stop": threading.Event(),
+                         "finished": threading.Event()}
+        self._donotrun = False
+        self.__timestamps = {}
+        self.__sleeptime = 2.0
+
+
+    def _log(self, logtype, msg):
+        "Common log function for rteval modules"
+        if self.__logger:
+            self.__logger.log(logtype, "[%s] %s" % (self._name, msg))
+
+
+    def isReady(self):
+        "Returns a boolean if the module is ready to run"
+        if self._donotrun:
+            return True
+        return self.__ready
+
+
+    def _setReady(self, state=True):
+        "Sets the ready flag for the module"
+        self.__ready = state
+
+
+    def hadRuntimeError(self):
+        "Returns a boolean if the module had a RuntimeError"
+        return self.__runtimeError
+
+
+    def _setRuntimeError(self, state=True):
+        "Sets the runtimeError flag for the module"
+        self.__runtimeError = state
+
+
+    def setStart(self):
+        "Sets the start event state"
+        self.__events["start"].set()
+        self.__timestamps["start_set"] = datetime.now()
+
+
+    def shouldStart(self):
+        "Returns the start event state - indicating the module can start"
+        return self.__events["start"].isSet()
+
+
+    def setStop(self):
+        "Sets the stop event state"
+        self.__events["stop"].set()
+        self.__timestamps["stop_set"] = datetime.now()
+
+
+    def shouldStop(self):
+        "Returns the stop event state - indicating the module should stop"
+        return self.__events["stop"].isSet()
+
+
+    def _setFinished(self):
+        "Sets the finished event state - indicating the module has completed"
+        self.__events["finished"].set()
+        self.__timestamps["finished_set"] = datetime.now()
+
+
+    def WaitForCompletion(self, wtime = None):
+        "Blocks until the module has completed its workload"
+        if not self.shouldStart():
+            # If it hasn't been started yet, nothing to wait for
+            return None
+        return self.__events["finished"].wait(wtime)
+
+
+    def _WorkloadSetup(self):
+        "Required module method, which purpose is to do the initial workload setup, preparing for _WorkloadBuild()"
+        raise NotImplementedError("_WorkloadSetup() method must be implemented in the %s module" % self._name)
+
+
+    def _WorkloadBuild(self):
+        "Required module method, which purpose is to compile additional code needed for the worklaod"
+        raise NotImplementedError("_WorkloadBuild() method must be implemented in the %s module" % self._name)
+
+
+    def _WorkloadPrepare(self):
+        "Required module method, which will initialise and prepare the workload just before it is about to start"
+        raise NotImplementedError("_WorkloadPrepare() method must be implemented in the %s module" % self._name)
+
+
+    def _WorkloadTask(self):
+        "Required module method, which kicks off the workload"
+        raise NotImplementedError("_WorkloadTask() method must be implemented in the %s module" % self._name)
+
+
+    def WorkloadAlive(self):
+        "Required module method, which should return True if the workload is still alive"
+        raise NotImplementedError("WorkloadAlive() method must be implemented in the %s module" % self._name)
+
+
+    def _WorkloadCleanup(self):
+        "Required module method, which will be run after the _WorkloadTask() has completed or been aborted by the 'stop event flag'"
+        raise NotImplementedError("_WorkloadCleanup() method must be implemented in the %s module" % self._name)
+
+
+    def WorkloadWillRun(self):
+        "Returns True if this workload will be run"
+        return self._donotrun is False
+
+
+    def run(self):
+        "Workload thread runner - takes care of keeping the workload running as long as needed"
+        if self.shouldStop():
+            return
+
+        # Initial workload setups
+        self._WorkloadSetup()
+
+        if not self._donotrun:
+            # Compile the workload
+            self._WorkloadBuild()
+
+            # Do final preparations of workload  before we're ready to start running
+            self._WorkloadPrepare()
+
+            # Wait until we're released
+            while True:
+                if self.shouldStop():
+                    return
+                self.__events["start"].wait(1.0)
+                if self.shouldStart():
+                    break
+
+            self._log(Log.DEBUG, "Starting %s workload" % self._module_type)
+            self.__timestamps["runloop_start"] = datetime.now()
+            while not self.shouldStop():
+                # Run the workload
+                self._WorkloadTask()
+
+                if self.shouldStop():
+                    break
+                if not self.WorkloadAlive():
+                    self._log(Log.DEBUG, "%s workload stopped running." % self._module_type)
+                    break
+                time.sleep(self.__sleeptime)
+
+            self.__timestamps["runloop_stop"] = datetime.now()
+            self._log(Log.DEBUG, "stopping %s workload" % self._module_type)
+        else:
+            self._log(Log.DEBUG, "Workload was not started")
+
+        self._WorkloadCleanup()
+
+
+    def MakeReport(self):
+        "required module method, needs to return an libxml2.xmlNode object with the the results from running"
+        raise NotImplementedError("MakeReport() method must be implemented in the%s module" % self._name)
+
+
+    def GetTimestamps(self):
+        "Return libxml2.xmlNode object with the gathered timestamps"
+
+        ts_n = libxml2.newNode("timestamps")
+        for k in self.__timestamps.keys():
+            ts_n.newChild(None, k, str(self.__timestamps[k]))
+
+        return ts_n
+
+
+
+class ModuleContainer(object):
+    """The ModuleContainer keeps an overview over loaded modules and the objects it
+will instantiate.  These objects are accessed by iterating the ModuleContainer object."""
+
+    def __init__(self, modules_root, logger):
+        """Creates a ModuleContainer object.  modules_root defines the default
+directory where the modules will be loaded from.  logger should point to a Log()
+object which will be used for logging and will also be given to the instantiated
+objects during module import."""
+        if logger and not isinstance(logger, Log):
+            raise TypeError("logger attribute is not a Log() object")
+
+        self.__modules_root = modules_root
+        self.__modtype = modules_root.split('.')[-1]
+        self.__logger = logger
+        self.__modobjects = {}  # Keeps track of instantiated objects
+        self.__modsloaded = {}     # Keeps track of imported modules
+        self.__iter_list = None
+
+
+    def LoadModule(self, modname, modroot=None):
+        """Imports a module and saves references to the imported module.
+If the same module is tried imported more times, it will return the module
+reference from the first import"""
+
+        if modroot is None:
+            modroot = self.__modules_root
+
+        # If this module is already reported return the module,
+        # if not (except KeyError:) import it and return the imported module
+        try:
+            idxname = "%s.%s" % (modroot, modname)
+            return self.__modsloaded[idxname]
+        except KeyError:
+            self.__logger.log(Log.INFO, "importing module %s" % modname)
+            mod = __import__("rteval.%s.%s" % (modroot, modname),
+                             fromlist="rteval.%s" % modroot)
+            self.__modsloaded[idxname] = mod
+            return mod
+
+
+    def ModuleInfo(self, modname, modroot = None):
+        """Imports a module and calls the modules' ModuleInfo() function and returns
+the information provided by the module"""
+
+        mod = self.LoadModule(modname, modroot)
+        return mod.ModuleInfo()
+
+
+    def SetupModuleOptions(self, parser, config):
+        """Sets up a separate optptarse OptionGroup per module with its supported parameters"""
+
+        grparser = optparse.OptionGroup(parser, "Group Options for %s modules" % self.__modtype)
+        grparser.add_option('--%s-cpulist' % self.__modtype,
+                            dest='%s___cpulist' % self.__modtype, action='store', default="",
+                            help='CPU list where %s modules will run' % self.__modtype,
+                            metavar='LIST')
+        parser.add_option_group(grparser)
+
+        for (modname, mod) in self.__modsloaded.items():
+            opts = mod.ModuleParameters()
+            if len(opts) == 0:
+                continue
+
+            shortmod = modname.split('.')[-1]
+            try:
+                cfg = config.GetSection(shortmod)
+            except KeyError:
+                # Ignore if a section is not found
+                cfg = None
+
+            grparser = optparse.OptionGroup(parser, "Options for the %s module" % shortmod)
+            for (o, s) in opts.items():
+                descr   = s.has_key('descr') and s['descr'] or ""
+                metavar = s.has_key('metavar') and s['metavar'] or None
+
+                try:
+                    default = cfg and getattr(cfg, o) or None
+                except AttributeError:
+                    # Ignore if this isn't found in the configuration object
+                    default = None
+
+                if default is None:
+                    default = s.has_key('default') and s['default'] or None
+
+
+                grparser.add_option('--%s-%s' % (shortmod, o),
+                                    dest="%s___%s" % (shortmod, o),
+                                    action='store',
+                                    help='%s%s' % (descr,
+                                                   default and ' (default: %s)' % default or ''),
+                                    default=default,
+                                    metavar=metavar)
+            parser.add_option_group(grparser)
+
+
+    def InstantiateModule(self, modname, modcfg, modroot = None):
+        """Imports a module and instantiates an object from the modules create() function.
+The instantiated object is returned in this call"""
+
+        if modcfg and not isinstance(modcfg, rtevalCfgSection):
+            raise TypeError("modcfg attribute is not a rtevalCfgSection() object")
+
+        mod = self.LoadModule(modname, modroot)
+        return mod.create(modcfg, self.__logger)
+
+
+    def RegisterModuleObject(self, modname, modobj):
+        """Registers an instantiated module object.  This module object will be
+returned when a ModuleContainer object is iterated over"""
+        self.__modobjects[modname] = modobj
+
+
+    def ExportModule(self, modname, modroot = None):
+        "Export module info, used to transfer an imported module to another ModuleContainer"
+        if modroot is None:
+            modroot = self.__modules_root
+
+        mod = "%s.%s" % (modroot, modname)
+        return (mod, self.__modsloaded[mod])
+
+
+    def ImportModule(self, module):
+        "Imports an exported module from another ModuleContainer"
+        (modname, moduleimp) = module
+        self.__modsloaded[modname] = moduleimp
+
+
+    def ModulesLoaded(self):
+        "Returns number of registered module objects"
+        return len(self.__modobjects)
+
+
+    def GetModulesList(self):
+        "Returns a list of module names"
+        return self.__modobjects.keys()
+
+
+    def GetNamedModuleObject(self, modname):
+        "Looks up a named module and returns its registered module object"
+        return self.__modobjects[modname]
+
+
+    def __iter__(self):
+        "Initiates the iterating process"
+
+        self.__iter_list = self.__modobjects.keys()
+        return self
+
+
+    def next(self):
+        """Internal Python iterating method, returns the next
+module name and object to be processed"""
+
+        if len(self.__iter_list) == 0:
+            self.__iter_list = None
+            raise StopIteration
+        else:
+            modname = self.__iter_list.pop()
+            return (modname, self.__modobjects[modname])
+
+
+
+class RtEvalModules(object):
+    """RtEvalModules should normally be inherrited by a more specific module class.
+    This class takes care of managing imported modules and have methods for starting
+    and stopping the workload these modules contains."""
+
+    def __init__(self, config, modules_root, logger):
+        """Initialises the RtEvalModules() internal variables.  The modules_root
+argument should point at the root directory where the modules will be loaded from.
+The logger argument should point to a Log() object which will be used for logging
+and will also be given to the instantiated objects during module import."""
+
+        self._cfg = config
+        self._logger = logger
+        self.__modules = ModuleContainer(modules_root, logger)
+        self.__timestamps = {}
+
+
+    # Export some of the internal module container methods
+    # Primarily to have better control of the module containers
+    # iteration API
+    def _ImportModule(self, module):
+        "Imports a module exported by ModuleContainer::ExportModule()"
+        return self.__modules.ImportModule(module)
+
+    def _InstantiateModule(self, modname, modcfg, modroot = None):
+        "Imports a module and returns an instantiated object from the module"
+        return self.__modules.InstantiateModule(modname, modcfg, modroot)
+
+    def _RegisterModuleObject(self, modname, modobj):
+        "Registers an instantiated module object which RtEvalModules will control"
+        return self.__modules.RegisterModuleObject(modname, modobj)
+
+    def _LoadModule(self, modname, modroot=None):
+        "Loads and imports a module"
+        return self.__modules.LoadModule(modname, modroot)
+
+    def ModulesLoaded(self):
+        "Returns number of imported modules"
+        return self.__modules.ModulesLoaded()
+
+    def GetModulesList(self):
+        "Returns a list of module names"
+        return self.__modules.GetModulesList()
+
+    def SetupModuleOptions(self, parser):
+        "Sets up optparse based option groups for the loaded modules"
+        return self.__modules.SetupModuleOptions(parser, self._cfg)
+
+    def GetNamedModuleObject(self, modname):
+        "Returns a list of module names"
+        return self.__modules.GetNamedModuleObject(modname)
+    # End of exports
+
+
+    def Start(self):
+        """Prepares all the imported modules workload to start, but they will not
+start their workloads yet"""
+
+        if self.__modules.ModulesLoaded() == 0:
+            raise rtevalRuntimeError("No %s modules configured" % self._module_type)
+
+        self._logger.log(Log.INFO, "Preparing %s modules" % self._module_type)
+        for (modname, mod) in self.__modules:
+            mod.start()
+            if mod.WorkloadWillRun():
+                self._logger.log(Log.DEBUG, "\t - Started %s preparations" % modname)
+
+        self._logger.log(Log.DEBUG, "Waiting for all %s modules to get ready" % self._module_type)
+        busy = True
+        while busy:
+            busy = False
+            for (modname, mod) in self.__modules:
+                if not mod.isReady():
+                    if not mod.hadRuntimeError():
+                        busy = True
+                        self._logger.log(Log.DEBUG, "Waiting for %s" % modname)
+                    else:
+                        raise RuntimeError("Runtime error starting the %s %s module" % (modname, self._module_type))
+
+            if busy:
+                time.sleep(1)
+
+        self._logger.log(Log.DEBUG, "All %s modules are ready" % self._module_type)
+
+
+    def hadError(self):
+        "Returns True if one or more modules had a RuntimeError"
+        return self.__runtimeError
+
+
+    def Unleash(self):
+        """Unleashes all the loaded modules workloads"""
+
+        # turn loose the loads
+        nthreads = 0
+        self._logger.log(Log.INFO, "Sending start event to all %s modules" % self._module_type)
+        for (modname, mod) in self.__modules:
+            mod.setStart()
+            nthreads += 1
+
+        self.__timestamps['unleash'] = datetime.now()
+        return nthreads
+
+
+    def _isAlive(self):
+        """Returns True if all modules are running"""
+
+        for (modname, mod) in self.__modules:
+            # We requiring all modules to run to pass
+            if not mod.WorkloadAlive():
+                return False
+        return True
+
+
+    def Stop(self):
+        """Stops all the running workloads from in all the loaded modules"""
+
+        if self.ModulesLoaded() == 0:
+            raise RuntimeError("No %s modules configured" % self._module_type)
+
+        self._logger.log(Log.INFO, "Stopping %s modules" % self._module_type)
+        for (modname, mod) in self.__modules:
+            if not mod.WorkloadWillRun():
+                continue
+
+            mod.setStop()
+            try:
+                self._logger.log(Log.DEBUG, "\t - Stopping %s" % modname)
+                if mod.is_alive():
+                    mod.join(2.0)
+            except RuntimeError, e:
+                self._logger.log(Log.ERR, "\t\tFailed stopping %s: %s" % (modname, str(e)))
+        self.__timestamps['stop'] = datetime.now()
+
+
+    def WaitForCompletion(self, wtime = None):
+        """Waits for the running modules to complete their running"""
+
+        self._logger.log(Log.INFO, "Waiting for %s modules to complete" % self._module_type)
+        for (modname, mod) in self.__modules:
+            self._logger.log(Log.DEBUG, "\t - Waiting for %s" % modname)
+            mod.WaitForCompletion(wtime)
+        self._logger.log(Log.DEBUG, "All %s modules completed" % self._module_type)
+
+
+    def MakeReport(self):
+        """Collects all the loaded modules reports in a single libxml2.xmlNode() object"""
+
+        rep_n = libxml2.newNode(self._report_tag)
+
+        for (modname, mod) in self.__modules:
+            self._logger.log(Log.DEBUG, "Getting report from %s" % modname)
+            modrep_n = mod.MakeReport()
+            if modrep_n is not None:
+                if self._module_type != 'load':
+                    # Currently the <loads/> tag will not easily integrate
+                    # timestamps. Not sure it makes sense to track this on
+                    # load modules.
+                    modrep_n.addChild(mod.GetTimestamps())
+                rep_n.addChild(modrep_n)
+
+        return rep_n
diff --git a/rteval/modules/loads/__init__.py b/rteval/modules/loads/__init__.py
new file mode 100644
index 0000000..040acc0
--- /dev/null
+++ b/rteval/modules/loads/__init__.py
@@ -0,0 +1,146 @@
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2012 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import os
+import time
+import threading
+import libxml2
+from rteval.Log import Log
+from rteval.rtevalConfig import rtevalCfgSection
+from rteval.modules import RtEvalModules, rtevalModulePrototype
+
+class LoadThread(rtevalModulePrototype):
+    def __init__(self, name, config, logger=None):
+
+        if name is None or not isinstance(name, str):
+            raise TypeError("name attribute is not a string")
+
+        if config and not isinstance(config, rtevalCfgSection):
+            raise TypeError("config attribute is not a rtevalCfgSection() object")
+
+        if logger and not isinstance(logger, Log):
+            raise TypeError("logger attribute is not a Log() object")
+
+        rtevalModulePrototype.__init__(self, "load", name, logger)
+        self.builddir = config.setdefault('builddir', os.path.abspath("../build"))	# abs path to top dir
+        self.srcdir = config.setdefault('srcdir', os.path.abspath("../loadsource"))	# abs path to src dir
+        self.num_cpus = config.setdefault('numcores', 1)
+        self.source = config.setdefault('source', None)
+        self.reportdir = config.setdefault('reportdir', os.getcwd())
+        self.memsize = config.setdefault('memsize', (0, 'GB'))
+        self.cpulist = config.setdefault('cpulist', "")
+        self._logging = config.setdefault('logging', True)
+        self._cfg = config
+        self.mydir = None
+        self.jobs = 0
+        self.args = None
+
+        if not os.path.exists(self.builddir):
+            os.makedirs(self.builddir)
+
+
+    def open_logfile(self, name):
+        return os.open(os.path.join(self.reportdir, "logs", name), os.O_CREAT|os.O_WRONLY)
+
+
+class CommandLineLoad(LoadThread):
+    def __init__(self, name, config, logger):
+        LoadThread.__init__(self, name, config, logger)
+
+
+    def MakeReport(self):
+        if not (self.jobs and self.args) or self._donotrun:
+            return None
+
+        rep_n = libxml2.newNode("command_line")
+        rep_n.newProp("name", self._name)
+
+        if self.jobs:
+            rep_n.newProp("job_instances", str(self.jobs))
+            if self.args:
+                rep_n.addContent(" ".join(self.args))
+
+        return rep_n
+
+
+class LoadModules(RtEvalModules):
+    """Module container for LoadThread based modules"""
+
+    def __init__(self, config, logger):
+        self._module_type = "load"
+        self._module_config = "loads"
+        self._report_tag = "loads"
+        self.__loadavg_accum = 0.0
+        self.__loadavg_samples = 0
+        RtEvalModules.__init__(self, config, "modules.loads", logger)
+        self.__LoadModules(self._cfg.GetSection(self._module_config))
+
+
+    def __LoadModules(self, modcfg):
+        "Loads and imports all the configured modules"
+
+        for m in modcfg:
+            # hope to eventually have different kinds but module is only on
+            # for now (jcw)
+            if m[1].lower() == 'module':
+                self._LoadModule(m[0])
+
+
+    def Setup(self, modparams):
+        if not isinstance(modparams, dict):
+            raise TypeError("modparams attribute is not of a dictionary type")
+
+        modcfg = self._cfg.GetSection(self._module_config)
+        cpulist = modcfg.cpulist
+        for m in modcfg:
+            # hope to eventually have different kinds but module is only on
+            # for now (jcw)
+            if m[1].lower() == 'module':
+                self._cfg.AppendConfig(m[0], modparams)
+                self._cfg.AppendConfig(m[0], {'cpulist': cpulist})
+                modobj = self._InstantiateModule(m[0], self._cfg.GetSection(m[0]))
+                self._RegisterModuleObject(m[0], modobj)
+
+
+    def MakeReport(self):
+        rep_n = RtEvalModules.MakeReport(self)
+        rep_n.newProp("load_average", str(self.GetLoadAvg()))
+
+        return rep_n
+
+
+    def SaveLoadAvg(self):
+        # open the loadavg /proc entry
+        p = open("/proc/loadavg")
+        load = float(p.readline().split()[0])
+        p.close()
+        self.__loadavg_accum += load
+        self.__loadavg_samples += 1
+
+
+    def GetLoadAvg(self):
+        if self.__loadavg_samples == 0:
+            self.SaveLoadAvg()
+        return float(self.__loadavg_accum / self.__loadavg_samples)
diff --git a/rteval/modules/loads/hackbench.py b/rteval/modules/loads/hackbench.py
new file mode 100644
index 0000000..8456a24
--- /dev/null
+++ b/rteval/modules/loads/hackbench.py
@@ -0,0 +1,206 @@
+
+#   hackbench.py - class to manage an instance of hackbench load
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import sys, os, time, glob, subprocess, errno, os.path
+from signal import SIGKILL
+from rteval.modules.loads import CommandLineLoad
+from rteval.Log import Log
+from rteval.misc import expand_cpulist
+
+class Hackbench(CommandLineLoad):
+    def __init__(self, config, logger):
+        CommandLineLoad.__init__(self, "hackbench", config, logger)
+
+
+    def _WorkloadSetup(self):
+        'calculate arguments based on input parameters'
+        (mem, units) = self.memsize
+        if units == 'KB':
+            mem = mem / (1024.0 * 1024.0)
+        elif units == 'MB':
+            mem = mem / 1024.0
+        elif units == 'TB':
+            mem = mem * 1024
+        ratio = float(mem) / float(self.num_cpus)
+        if ratio >= 0.75:
+            mult = float(self._cfg.setdefault('jobspercore', 2))
+        else:
+            self._log(Log.INFO, "Low memory system (%f GB/core)! Not running" % ratio)
+            mult = 0
+            self._donotrun = True
+
+        # figure out how many nodes we have
+        self.nodes = [ n.split('/')[-1][4:] for n in glob.glob('/sys/devices/system/node/node*') ]
+
+
+        # get the cpus for each node
+        self.cpus = {}
+        biggest = 0
+        for n in self.nodes:
+            self.cpus[n] = [ int(c.split('/')[-1][3:]) for c in glob.glob('/sys/devices/system/node/node%s/cpu[0-9]*' % n) ]
+            self.cpus[n].sort()
+
+            # if a cpulist was specified, only allow cpus in that list on the node
+            if self.cpulist:
+                self.cpus[n] = [ c for c in self.cpus[n] if c in expand_cpulist(self.cpulist) ]
+
+            # track largest number of cpus used on a node
+            if len(self.cpus[n]) > biggest:
+                biggest = len(self.cpus[n])
+
+        # setup jobs based on the number of cores available per node
+        self.jobs = biggest * 3
+
+        # figure out if we can use numactl or have to use taskset
+        self.__usenumactl = False
+        self.__multinodes = False
+        if len(self.nodes) > 1:
+            self.__multinodes = True
+            self._log(Log.INFO, "running with multiple nodes (%d)" % len(self.nodes))
+            if os.path.exists('/usr/bin/numactl'):
+                self.__usenumactl = True
+                self._log(Log.INFO, "using numactl for thread affinity")
+
+        self.args = ['hackbench',  '-P',
+                     '-g', str(self.jobs),
+                     '-l', str(self._cfg.setdefault('loops', '1000')),
+                     '-s', str(self._cfg.setdefault('datasize', '1000'))
+                     ]
+        self.__err_sleep = 5.0
+
+    def _WorkloadBuild(self):
+        # Nothing to build, so we're basically ready
+        self._setReady()
+
+
+    def _WorkloadPrepare(self):
+        self.__nullfp = os.open("/dev/null", os.O_RDWR)
+        if self._logging:
+            self.__out = self.open_logfile("hackbench.stdout")
+            self.__err = self.open_logfile("hackbench.stderr")
+        else:
+            self.__out = self.__err = self.__nullfp
+
+        self.tasks = {}
+
+        self._log(Log.DEBUG, "starting loop (jobs: %d)" % self.jobs)
+
+        self.started = False
+
+    def __starton(self, node):
+        if self.__multinodes or self.cpulist:
+            if self.__usenumactl:
+                args = [ 'numactl', '--cpunodebind', node ] + self.args
+            else:
+                cpulist = ",".join([ str(n) for n in self.cpus[node] ])
+                args = ['taskset', '-c', cpulist ] + self.args
+        else:
+            args = self.args
+
+        self._log(Log.DEBUG, "starting on node %s: args = %s" % (node, args))
+        p = subprocess.Popen(args,
+                             stdin=self.__nullfp,
+                             stdout=self.__out,
+                             stderr=self.__err)
+        if not p:
+            self._log(Log.DEBUG, "hackbench failed to start on node %s" % node)
+            raise RuntimeError, "hackbench failed to start on node %s" % node
+        return p
+
+    def _WorkloadTask(self):
+        if self.shouldStop():
+            return
+
+        # just do this once
+        if not self.started:
+            for n in self.nodes:
+                self.tasks[n] = self.__starton(n)
+            self.started = True
+            return
+
+        for n in self.nodes:
+            try:
+                if self.tasks[n].poll() is not None:
+                    self.tasks[n].wait()
+                    self.tasks[n] = self.__starton(n)
+            except OSError, e:
+                if e.errno != errno.ENOMEM:
+                    raise e
+                # Catch out-of-memory errors and wait a bit to (hopefully)
+                # ease memory pressure
+                self._log(Log.DEBUG, "ERROR: %s, sleeping for %f seconds" % (e.strerror, self.__err_sleep))
+                time.sleep(self.__err_sleep)
+                if self.__err_sleep < 60.0:
+                    self.__err_sleep *= 2.0
+                if self.__err_sleep > 60.0:
+                    self.__err_sleep = 60.0
+
+
+    def WorkloadAlive(self):
+        # As hackbench is short-lived, lets pretend it is always alive
+        return True
+
+
+    def _WorkloadCleanup(self):
+        if self._donotrun:
+            return
+
+        for node in self.nodes:
+            if self.tasks.has_key(node) and self.tasks[node].poll() is None:
+                self._log(Log.INFO, "cleaning up hackbench on node %s" % node)
+                self.tasks[node].send_signal(SIGKILL)
+                if self.tasks[node].poll() == None:
+                    time.sleep(2)
+            self.tasks[node].wait()
+            del self.tasks[node]
+
+        os.close(self.__nullfp)
+        if self._logging:
+            os.close(self.__out)
+            del self.__out
+            os.close(self.__err)
+            del self.__err
+
+        del self.__nullfp
+
+
+
+def ModuleParameters():
+    return {"jobspercore": {"descr": "Number of working threads per CPU core",
+                            "default": 5,
+                            "metavar": "NUM"},
+            }
+
+
+
+def create(config, logger):
+    return Hackbench(config, logger)
+
+
+if __name__ == '__main__':
+    h = Hackbench(params={'debugging':True, 'verbose':True})
+    h.run()
diff --git a/rteval/modules/loads/kcompile.py b/rteval/modules/loads/kcompile.py
new file mode 100644
index 0000000..ef636c2
--- /dev/null
+++ b/rteval/modules/loads/kcompile.py
@@ -0,0 +1,252 @@
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2012 - 2013   David Sommerseth <davids@redhat.com>
+#   Copyright 2014 - 2017   Clark Williams <williams@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import sys, os, os.path, glob, subprocess
+from signal import SIGTERM
+from rteval.modules import rtevalRuntimeError
+from rteval.modules.loads import CommandLineLoad
+from rteval.Log import Log
+from rteval.misc import expand_cpulist
+from rteval.systopology import SysTopology
+
+kernel_prefix="linux-4.9"
+
+class KBuildJob(object):
+    '''Class to manage a build job bound to a particular node'''
+
+    def __init__(self, node, kdir, logger=None):
+        self.kdir = kdir
+        self.jobid = None
+        self.node = node
+        self.logger = logger
+        self.builddir = os.path.dirname(kdir)
+        self.objdir = "%s/node%d" % (self.builddir, int(node))
+        if not os.path.isdir(self.objdir):
+            os.mkdir(self.objdir)
+        if os.path.exists('/usr/bin/numactl'):
+            self.binder = 'numactl --cpunodebind %d' % int(self.node)
+        else:
+            self.binder = 'taskset -c %s' % str(self.node)
+        self.jobs = self.calc_jobs_per_cpu() * len(self.node)
+        self.log(Log.DEBUG, "node %d: jobs == %d" % (int(node), self.jobs))
+        self.runcmd = "%s make O=%s -C %s -j%d bzImage modules" % (self.binder, self.objdir, self.kdir, self.jobs)
+        self.cleancmd = "%s make O=%s -C %s clean allmodconfig" % (self.binder, self.objdir, self.kdir)
+        self.log(Log.DEBUG, "node%d kcompile command: %s" % (int(node), self.runcmd))
+
+    def __str__(self):
+        return self.runcmd
+
+    def log(self, logtype, msg):
+        if self.logger:
+            self.logger.log(logtype, "[kcompile node%d] %s" % (int(self.node), msg))
+
+    def calc_jobs_per_cpu(self):
+        mult = 2
+        self.log(Log.DEBUG, "calulating jobs for node %d" % int(self.node))
+        # get memory total in gigabytes
+        mem = int(self.node.meminfo['MemTotal']) / 1024.0 / 1024.0 / 1024.0
+        # ratio of gigabytes to #cores
+        ratio = float(mem) / float(len(self.node))
+        if ratio < 1.0:
+            ratio = 1.0
+        if ratio < 1.0 or ratio > 2.0:
+            mult = 1
+        self.log(Log.DEBUG, "memory/cores ratio on node %d: %f" % (int(self.node), ratio))
+        self.log(Log.DEBUG, "returning jobs/core value of: %d" % int(ratio) * mult)
+        return int(int(ratio) * int(mult))
+
+    def clean(self, sin=None, sout=None, serr=None):
+        self.log(Log.DEBUG, "cleaning objdir %s" % self.objdir)
+        subprocess.call(self.cleancmd, shell=True,
+                        stdin=sin, stdout=sout, stderr=serr)
+
+    def run(self, sin=None, sout=None, serr=None):
+        self.log(Log.INFO, "starting workload on node %d" % int(self.node))
+        self.log(Log.DEBUG, "running on node %d: %s" % (int(self.node), self.runcmd))
+        self.jobid = subprocess.Popen(self.runcmd, shell=True,
+                                      stdin=sin, stdout=sout, stderr=serr)
+
+    def isrunning(self):
+        if self.jobid == None:
+            return False
+        return (self.jobid.poll() == None)
+
+    def stop(self):
+        if not self.jobid:
+            return True
+        return self.jobid.terminate()
+
+
+class Kcompile(CommandLineLoad):
+    def __init__(self, config, logger):
+        self.buildjobs = {}
+        self.config = config
+        self.topology = SysTopology()
+        CommandLineLoad.__init__(self, "kcompile", config, logger)
+        self.logger = logger
+
+    def _WorkloadSetup(self):
+        # find our source tarball
+        if self._cfg.has_key('tarball'):
+            tarfile = os.path.join(self.srcdir, self._cfg.tarfile)
+            if not os.path.exists(tarfile):
+                raise rtevalRuntimeError(self, " tarfile %s does not exist!" % tarfile)
+            self.source = tarfile
+        else:
+            tarfiles = glob.glob(os.path.join(self.srcdir, "%s*" % kernel_prefix))
+            if len(tarfiles):
+                self.source = tarfiles[0]
+            else:
+                raise rtevalRuntimeError(self, " no kernel tarballs found in %s" % self.srcdir)
+
+        # check for existing directory
+        kdir=None
+        names=os.listdir(self.builddir)
+        for d in names:
+            if d.startswith(kernel_prefix):
+                kdir=d
+                break
+        if kdir == None:
+            self._log(Log.DEBUG, "unpacking kernel tarball")
+            tarargs = ['tar', '-C', self.builddir, '-x']
+            if self.source.endswith(".bz2"):
+                tarargs.append("-j")
+            elif self.source.endswith(".gz"):
+                tarargs.append("-z")
+            tarargs.append("-f")
+            tarargs.append(self.source)
+            try:
+                subprocess.call(tarargs)
+            except:
+                self._log(Log.DEBUG, "untar'ing kernel self.source failed!")
+                sys.exit(-1)
+            names = os.listdir(self.builddir)
+            for d in names:
+                self._log(Log.DEBUG, "checking %s" % d)
+                if d.startswith(kernel_prefix):
+                    kdir=d
+                    break
+        if kdir == None:
+            raise rtevalRuntimeError(self, "Can't find kernel directory!")
+        self.mydir = os.path.join(self.builddir, kdir)
+        self._log(Log.DEBUG, "mydir = %s" % self.mydir)
+        self._log(Log.DEBUG, "systopology: %s" % self.topology)
+        self.jobs = len(self.topology)
+        self.args = []
+        for n in self.topology:
+            self._log(Log.DEBUG, "Configuring build job for node %d" % int(n))
+            self.buildjobs[n] = KBuildJob(n, self.mydir, self.logger)
+            self.args.append(str(self.buildjobs[n])+";")
+
+
+    def _WorkloadBuild(self):
+        null = os.open("/dev/null", os.O_RDWR)
+        if self._logging:
+            out = self.open_logfile("kcompile-build.stdout")
+            err = self.open_logfile("kcompile-build.stderr")
+        else:
+            out = err = null
+
+        # clean up any damage from previous runs
+        try:
+            ret = subprocess.call(["make", "-C", self.mydir, "mrproper"],
+                                  stdin=null, stdout=out, stderr=err)
+            if ret:
+                raise rtevalRuntimeError(self, "kcompile setup failed: %d" % ret)
+        except KeyboardInterrupt, m:
+            self._log(Log.DEBUG, "keyboard interrupt, aborting")
+            return
+        self._log(Log.DEBUG, "ready to run")
+        if self._logging:
+            os.close(out)
+            os.close(err)
+        # clean up object dirs and make sure each has a config file
+        for n in self.topology:
+            self.buildjobs[n].clean(sin=null,sout=null,serr=null)
+        os.close(null)
+        self._setReady()
+
+    def _WorkloadPrepare(self):
+        self.__nullfd = os.open("/dev/null", os.O_RDWR)
+        if self._logging:
+            self.__outfd = self.open_logfile("kcompile.stdout")
+            self.__errfd = self.open_logfile("kcompile.stderr")
+        else:
+            self.__outfd = self.__errfd = self.__nullfd
+
+        if self._cfg.has_key('cpulist') and self._cfg.cpulist:
+            cpulist = self._cfg.cpulist
+            self.num_cpus = len(expand_cpulist(cpulist))
+        else:
+            cpulist = ""
+
+    def _WorkloadTask(self):
+        for n in self.topology:
+            if not self.buildjobs[n]:
+                raise RuntimeError, "Build job not set up for node %d" % int(n)
+            if self.buildjobs[n].jobid is None or self.buildjobs[n].jobid.poll() is not None:
+                self._log(Log.INFO, "Starting load on node %d" % n)
+                self.buildjobs[n].run(self.__nullfd, self.__outfd, self.__errfd)
+
+    def WorkloadAlive(self):
+        # if any of the jobs has stopped, return False
+        for n in self.topology:
+            if self.buildjobs[n].jobid.poll() is not None:
+                return False
+        return True
+
+
+    def _WorkloadCleanup(self):
+        self._log(Log.DEBUG, "out of stopevent loop")
+        for n in self.buildjobs:
+            if self.buildjobs[n].jobid.poll() == None:
+                self._log(Log.DEBUG, "stopping job on node %d" % int(n))
+                self.buildjobs[n].jobid.terminate()
+                self.buildjobs[n].jobid.wait()
+                del self.buildjobs[n].jobid
+        os.close(self.__nullfd)
+        del self.__nullfd
+        if self._logging:
+            os.close(self.__outfd)
+            del self.__outfd
+            os.close(self.__errfd)
+            del self.__errfd
+        self._setFinished()
+
+
+def ModuleParameters():
+    return {"source":   {"descr": "Source tar ball",
+                         "default": "linux-4.9.tar.xz",
+                         "metavar": "TARBALL"},
+            "jobspercore": {"descr": "Number of working threads per core",
+                            "default": 2,
+                            "metavar": "NUM"},
+            }
+
+
+
+def create(config, logger):
+    return Kcompile(config, logger)
diff --git a/rteval/modules/measurement/__init__.py b/rteval/modules/measurement/__init__.py
new file mode 100644
index 0000000..b382971
--- /dev/null
+++ b/rteval/modules/measurement/__init__.py
@@ -0,0 +1,216 @@
+#
+#   Copyright 2012 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import libxml2
+from rteval.modules import RtEvalModules, ModuleContainer
+
+
+class MeasurementProfile(RtEvalModules):
+    """Keeps and controls all the measurement modules with the same measurement profile"""
+
+    def __init__(self, config, with_load, run_parallel, modules_root, logger):
+        self.__with_load = with_load
+        self.__run_parallel = run_parallel
+
+        # Only used when running modules serialised
+        self.__run_serialised_mods = None
+
+        self._module_type = "measurement"
+        self._module_config = "measurement"
+        self._report_tag = "Profile"
+        RtEvalModules.__init__(self, config, modules_root, logger)
+
+
+    def GetProfile(self):
+        "Returns the profile characteristic as (with_load, run_parallel)"
+        return (self.__with_load, self.__run_parallel)
+
+
+    def ImportModule(self, module):
+        "Imports an exported module from a ModuleContainer() class"
+        return self._ImportModule(module)
+
+
+    def Setup(self, modname):
+        "Instantiates and prepares a measurement module"
+
+        modobj = self._InstantiateModule(modname, self._cfg.GetSection(modname))
+        self._RegisterModuleObject(modname, modobj)
+
+
+    def Unleash(self):
+        """Unleashes all the measurement modules"""
+
+        if self.__run_parallel:
+            # Use the inherrited method if running
+            # measurements in parallel
+            return RtEvalModules.Unleash(self)
+
+        # Get a list of all registered modules,
+        # and start the first one
+        self.__serialised_mods = self.GetModulesList()
+        mod = self.GetNamedModuleObject(self.__serialised_mods[0])
+        mod.setStart()
+        return 1
+
+
+    def MakeReport(self):
+        "Generates an XML report for all run measurement modules in this profile"
+        rep_n = RtEvalModules.MakeReport(self)
+        rep_n.newProp("loads", self.__with_load and "1" or "0")
+        rep_n.newProp("parallel", self.__run_parallel and "1" or "0")
+        return rep_n
+
+
+    def isAlive(self):
+        """Returns True if all modules which are supposed to run runs"""
+
+        if self.__run_parallel:
+            return self._isAlive()
+
+        if len(self.__serialised_mods) > 0:
+            # If running serialised, first check if measurement is still running,
+            # if so - return True.
+            mod = self.GetNamedModuleObject(self.__serialised_mods[0])
+            if mod.WorkloadAlive():
+                return True
+
+            # If not, go to next on the list and kick it off
+            self.__serialised_mods.remove(self.__serialised_mods[0])
+            if len(self.__serialised_mods) > 0:
+                mod = self.GetNamedModuleObject(self.__serialised_mods[0])
+                mod.setStart()
+                return True
+
+        # If we've been through everything, nothing is running
+        return False
+
+
+class MeasurementModules(object):
+    """Class which takes care of all measurement modules and groups them into
+measurement profiles, based on their characteristics"""
+
+    def __init__(self, config, logger):
+        self.__cfg = config
+        self.__logger = logger
+        self.__measureprofiles = []
+        self.__modules_root = "modules.measurement"
+        self.__iter_item = None
+
+        # Temporary module container, which is used to evalute measurement modules.
+        # This will container will be destroyed after Setup() has been called
+        self.__container = ModuleContainer(self.__modules_root, self.__logger)
+        self.__LoadModules(self.__cfg.GetSection("measurement"))
+
+
+    def __LoadModules(self, modcfg):
+        "Loads and imports all the configured modules"
+
+        for m in modcfg:
+            # hope to eventually have different kinds but module is only on
+            # for now (jcw)
+            if m[1].lower() == 'module':
+                self.__container.LoadModule(m[0])
+
+
+    def GetProfile(self, with_load, run_parallel):
+        "Returns the appropriate MeasurementProfile object, based on the profile type"
+
+        for p in self.__measureprofiles:
+            mp = p.GetProfile()
+            if mp == (with_load, run_parallel):
+                return p
+        return None
+
+
+    def SetupModuleOptions(self, parser):
+        "Sets up all the measurement modules' parameters for the option parser"
+        self.__container.SetupModuleOptions(parser, self.__cfg)
+
+
+    def Setup(self, modparams):
+        "Loads all measurement modules and group them into different measurement profiles"
+
+        if not isinstance(modparams, dict):
+            raise TypeError("modparams attribute is not of a dictionary type")
+
+        modcfg = self.__cfg.GetSection("measurement")
+        cpulist = modcfg.cpulist
+
+        for (modname, modtype) in modcfg:
+            if modtype.lower() == 'module':  # Only 'module' will be supported (ds)
+                # Extract the measurement modules info
+                modinfo = self.__container.ModuleInfo(modname)
+
+                # Get the correct measurement profile container for this module
+                mp = self.GetProfile(modinfo["loads"], modinfo["parallel"])
+                if mp is None:
+                    # If not found, create a new measurement profile
+                    mp = MeasurementProfile(self.__cfg,
+                                            modinfo["loads"], modinfo["parallel"],
+                                            self.__modules_root, self.__logger)
+                    self.__measureprofiles.append(mp)
+
+                    # Export the module imported here and transfer it to the
+                    # measurement profile
+                    mp.ImportModule(self.__container.ExportModule(modname))
+
+                # Setup this imported module inside the appropriate measurement profile
+                self.__cfg.AppendConfig(modname, modparams)
+                self.__cfg.AppendConfig(modname, {'cpulist':cpulist})
+                mp.Setup(modname)
+
+        del self.__container
+
+
+    def MakeReport(self):
+        "Generates an XML report for all measurement profiles"
+
+        # Get the reports from all meaurement modules in all measurement profiles
+        rep_n = libxml2.newNode("Measurements")
+        for mp in self.__measureprofiles:
+            mprep_n = mp.MakeReport()
+            if mprep_n:
+                rep_n.addChild(mprep_n)
+
+        return rep_n
+
+
+    def __iter__(self):
+        "Initiates an iteration loop for MeasurementProfile objects"
+
+        self.__iter_item = len(self.__measureprofiles)
+        return self
+
+
+    def next(self):
+        """Internal Python iterating method, returns the next
+MeasurementProfile object to be processed"""
+
+        if self.__iter_item == 0:
+            self.__iter_item = None
+            raise StopIteration
+        else:
+            self.__iter_item -= 1
+            return self.__measureprofiles[self.__iter_item]
diff --git a/rteval/modules/measurement/cyclictest.py b/rteval/modules/measurement/cyclictest.py
new file mode 100644
index 0000000..c5b3055
--- /dev/null
+++ b/rteval/modules/measurement/cyclictest.py
@@ -0,0 +1,448 @@
+#
+#   cyclictest.py - object to manage a cyclictest executable instance
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2012 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import os, sys, subprocess, signal, libxml2, shutil, tempfile, time
+from rteval.Log import Log
+from rteval.modules import rtevalModulePrototype
+from rteval.misc import expand_cpulist, online_cpus, cpuinfo
+
+class RunData(object):
+    '''class to keep instance data from a cyclictest run'''
+    def __init__(self, coreid, datatype, priority, logfnc):
+        self.__id = coreid
+        self.__type = datatype
+        self.__priority = int(priority)
+        self.__description = ''
+        # histogram of data
+        self.__samples = {}
+        self.__numsamples = 0
+        self.__min = 100000000
+        self.__max = 0
+        self.__stddev = 0.0
+        self.__mean = 0.0
+        self.__mode = 0.0
+        self.__median = 0.0
+        self.__range = 0.0
+        self.__mad = 0.0
+        self._log = logfnc
+
+    def __str__(self):
+        retval =  "id:         %s\n" % self.__id
+        retval += "type:       %s\n" % self.__type
+        retval += "numsamples: %d\n" % self.__numsamples
+        retval += "min:        %d\n" % self.__min
+        retval += "max:        %d\n" % self.__max
+        retval += "stddev:     %f\n" % self.__stddev
+        retval += "mad:        %f\n" % self.__mad
+        retval += "mean:       %f\n" % self.__mean
+        return retval
+
+    def sample(self, value):
+        self.__samples[value] += self.__samples.setdefault(value, 0) + 1
+        if value > self.__max: self.__max = value
+        if value < self.__min: self.__min = value
+        self.__numsamples += 1
+
+    def bucket(self, index, value):
+        self.__samples[index] = self.__samples.setdefault(index, 0) + value
+        if value and index > self.__max: self.__max = index
+        if value and index < self.__min: self.__min = index
+        self.__numsamples += value
+
+    def reduce(self):
+        import math
+
+        # check to see if we have any samples and if we
+        # only have 1 (or none) set the calculated values
+        # to zero and return
+        if self.__numsamples <= 1:
+            self._log(Log.DEBUG, "skipping %s (%d samples)" % (self.__id, self.__numsamples))
+            self.__mad = 0
+            self.__stddev = 0
+            return
+
+        self._log(Log.INFO, "reducing %s" % self.__id)
+        total = 0
+        keys = self.__samples.keys()
+        keys.sort()
+        sorted = []
+
+        mid = self.__numsamples / 2
+
+        # mean, mode, and median
+        occurances = 0
+        lastkey = -1
+        for i in keys:
+            if mid > total and mid <= (total + self.__samples[i]):
+                if self.__numsamples & 1 and mid == total+1:
+                    self.__median = (lastkey + i) / 2
+                else:
+                    self.__median = i
+            total += (i * self.__samples[i])
+            if self.__samples[i] > occurances:
+                occurances = self.__samples[i]
+                self.__mode = i
+        self.__mean = float(total) / float(self.__numsamples)
+
+        # range
+        for i in keys:
+            if self.__samples[i]:
+                low = i
+                break
+        high = keys[-1]
+        while high and self.__samples[high] == 0:
+            high -= 1
+        self.__range = high - low
+
+        # Mean Absolute Deviation and standard deviation
+        madsum = 0
+        varsum = 0
+        for i in keys:
+            madsum += float(abs(float(i) - self.__mean) * self.__samples[i])
+            varsum += float(((float(i) - self.__mean) ** 2) * self.__samples[i])
+        self.__mad = madsum / self.__numsamples
+        self.__stddev = math.sqrt(varsum / (self.__numsamples - 1))
+
+
+    def MakeReport(self):
+        rep_n = libxml2.newNode(self.__type)
+        if self.__type == 'system':
+            rep_n.newProp('description', self.__description)
+        else:
+            rep_n.newProp('id', str(self.__id))
+            rep_n.newProp('priority', str(self.__priority))
+
+        stat_n = rep_n.newChild(None, 'statistics', None)
+
+        stat_n.newTextChild(None, 'samples', str(self.__numsamples))
+
+        if self.__numsamples > 0:
+            n = stat_n.newTextChild(None, 'minimum', str(self.__min))
+            n.newProp('unit', 'us')
+
+            n = stat_n.newTextChild(None, 'maximum', str(self.__max))
+            n.newProp('unit', 'us')
+
+            n = stat_n.newTextChild(None, 'median', str(self.__median))
+            n.newProp('unit', 'us')
+
+            n = stat_n.newTextChild(None, 'mode', str(self.__mode))
+            n.newProp('unit', 'us')
+
+            n = stat_n.newTextChild(None, 'range', str(self.__range))
+            n.newProp('unit', 'us')
+
+            n = stat_n.newTextChild(None, 'mean', str(self.__mean))
+            n.newProp('unit', 'us')
+
+            n = stat_n.newTextChild(None, 'mean_absolute_deviation', str(self.__mad))
+            n.newProp('unit', 'us')
+
+            n = stat_n.newTextChild(None, 'standard_deviation', str(self.__stddev))
+            n.newProp('unit', 'us')
+
+            hist_n = rep_n.newChild(None, 'histogram', None)
+            hist_n.newProp('nbuckets', str(len(self.__samples)))
+            keys = self.__samples.keys()
+            keys.sort()
+            for k in keys:
+                if self.__samples[k] == 0:
+                    # Don't report buckets without any samples
+                    continue
+                b_n = hist_n.newChild(None, 'bucket', None)
+                b_n.newProp('index', str(k))
+                b_n.newProp('value', str(self.__samples[k]))
+
+        return rep_n
+
+
+class Cyclictest(rtevalModulePrototype):
+    def __init__(self, config, logger=None):
+        rtevalModulePrototype.__init__(self, 'measurement', 'cyclictest', logger)
+        self.__cfg = config
+
+        # Create a RunData object per CPU core
+        self.__numanodes = int(self.__cfg.setdefault('numanodes', 0))
+        self.__priority = int(self.__cfg.setdefault('priority', 95))
+        self.__buckets = int(self.__cfg.setdefault('buckets', 2000))
+        self.__numcores = 0
+        self.__cpus = []
+        self.__cyclicdata = {}
+        self.__sparse = False
+
+        if self.__cfg.cpulist:
+            self.__cpulist = self.__cfg.cpulist
+            self.__cpus = expand_cpulist(self.__cpulist)
+            self.__sparse = True
+        else:
+            self.__cpus = online_cpus()
+
+        self.__numcores = len(self.__cpus)
+
+        info = cpuinfo()
+
+        # create a RunData object for each core we'll measure
+        for core in self.__cpus:
+            self.__cyclicdata[core] = RunData(core, 'core',self.__priority,
+                                              logfnc=self._log)
+            self.__cyclicdata[core].description = info[core]['model name']
+
+        # Create a RunData object for the overall system
+        self.__cyclicdata['system'] = RunData('system', 'system', self.__priority,
+                                              logfnc=self._log)
+        self.__cyclicdata['system'].description = ("(%d cores) " % self.__numcores) + info['0']['model name']
+
+        if self.__sparse:
+            self._log(Log.DEBUG, "system using %d cpu cores" % self.__numcores)
+        else:
+            self._log(Log.DEBUG, "system has %d cpu cores" % self.__numcores)
+        self.__started = False
+        self.__cyclicoutput = None
+        self.__breaktraceval = None
+
+
+    def __getmode(self):
+        if self.__numanodes > 1:
+            self._log(Log.DEBUG, "running in NUMA mode (%d nodes)" % self.__numanodes)
+            return '--numa'
+        self._log(Log.DEBUG, "running in SMP mode")
+        return '--smp'
+
+
+    def __get_debugfs_mount(self):
+        ret = None
+        mounts = open('/proc/mounts')
+        for l in mounts:
+            field = l.split()
+            if field[2] == "debugfs":
+                ret = field[1]
+                break
+        mounts.close()
+        return ret
+
+
+    def _WorkloadSetup(self):
+        self.__cyclicprocess = None
+        pass
+
+
+    def _WorkloadBuild(self):
+        self._setReady()
+
+
+    def _WorkloadPrepare(self):
+        self.__interval = self.__cfg.has_key('interval') and '-i%d' % int(self.__cfg.interval) or ""
+
+        self.__cmd = ['cyclictest',
+                      self.__interval,
+                      '-qmun',
+                      '-h %d' % self.__buckets,
+                      "-p%d" % int(self.__priority),
+                      ]
+        if self.__sparse:
+            self.__cmd.append('-t%d' % self.__numcores)
+            self.__cmd.append('-a%s' % self.__cpulist)
+        else:
+            self.__cmd.append(self.__getmode())
+
+        if self.__cfg.has_key('threads') and self.__cfg.threads:
+            self.__cmd.append("-t%d" % int(self.__cfg.threads))
+
+        if self.__cfg.has_key('breaktrace') and self.__cfg.breaktrace:
+            self.__cmd.append("-b%d" % int(self.__cfg.breaktrace))
+            self.__cmd.append("--tracemark")
+            self.__cmd.append("--notrace")
+
+        # Buffer for cyclictest data written to stdout
+        self.__cyclicoutput = tempfile.SpooledTemporaryFile(mode='rw+b')
+
+
+    def _WorkloadTask(self):
+        if self.__started:
+            # Don't restart cyclictest if it is already runing
+            return
+
+        self._log(Log.DEBUG, "starting with cmd: %s" % " ".join(self.__cmd))
+        self.__nullfp = os.open('/dev/null', os.O_RDWR)
+
+        debugdir = self.__get_debugfs_mount()
+        if self.__cfg.has_key('breaktrace') and self.__cfg.breaktrace and debugdir:
+            # Ensure that the trace log is clean
+            trace = os.path.join(debugdir, 'tracing', 'trace')
+            fp = open(os.path.join(trace), "w")
+            fp.write("0")
+            fp.flush()
+            fp.close()
+
+        self.__cyclicoutput.seek(0)
+        self.__cyclicprocess = subprocess.Popen(self.__cmd,
+                                                stdout=self.__cyclicoutput,
+                                                stderr=self.__nullfp,
+                                                stdin=self.__nullfp)
+        self.__started = True
+
+
+    def WorkloadAlive(self):
+        if self.__started:
+            return self.__cyclicprocess.poll() is None
+        else:
+            return False
+
+
+    def _WorkloadCleanup(self):
+        while self.__cyclicprocess.poll() == None:
+            self._log(Log.DEBUG, "Sending SIGINT")
+            os.kill(self.__cyclicprocess.pid, signal.SIGINT)
+            time.sleep(2)
+
+        # now parse the histogram output
+        self.__cyclicoutput.seek(0)
+        for line in self.__cyclicoutput:
+            if line.startswith('#'):
+                # Catch if cyclictest stopped due to a breaktrace
+                if line.startswith('# Break value: '):
+                    self.__breaktraceval = int(line.split(':')[1])
+                continue
+
+            # Skipping blank lines
+            if len(line) == 0:
+                continue
+
+            vals = line.split()
+            if len(vals) == 0:
+                # If we don't have any values, don't try parsing
+                continue
+
+            try:
+                index = int(vals[0])
+            except:
+                self._log(Log.DEBUG, "cyclictest: unexpected output: %s" % line)
+                continue
+
+            for i,core in enumerate(self.__cpus):
+                self.__cyclicdata[core].bucket(index, int(vals[i+1]))
+                self.__cyclicdata['system'].bucket(index, int(vals[i+1]))
+
+        # generate statistics for each RunData object
+        for n in self.__cyclicdata.keys():
+            #print "reducing self.__cyclicdata[%s]" % n
+            self.__cyclicdata[n].reduce()
+            #print self.__cyclicdata[n]
+
+        self._setFinished()
+        self.__started = False
+        os.close(self.__nullfp)
+        del self.__nullfp
+
+
+    def MakeReport(self):
+        rep_n = libxml2.newNode('cyclictest')
+        rep_n.newProp('command_line', ' '.join(self.__cmd))
+
+        # If it was detected cyclictest was aborted somehow,
+        # report the reason
+        abrt_n = libxml2.newNode('abort_report')
+        abrt = False
+        if self.__breaktraceval:
+            abrt_n.newProp('reason', 'breaktrace')
+            btv_n = abrt_n.newChild(None, 'breaktrace', None)
+            btv_n.newProp('latency_threshold', str(self.__cfg.breaktrace))
+            btv_n.newProp('measured_latency', str(self.__breaktraceval))
+            abrt = True
+
+        # Only add the <abort_report/> node if an abortion happened
+        if abrt:
+            rep_n.addChild(abrt_n)
+
+        rep_n.addChild(self.__cyclicdata["system"].MakeReport())
+        for thr in self.__cpus:
+            if str(thr) not in self.__cyclicdata:
+                continue
+            rep_n.addChild(self.__cyclicdata[str(thr)].MakeReport())
+
+        return rep_n
+
+
+
+def ModuleInfo():
+    return {"parallel": True,
+            "loads": True}
+
+
+
+def ModuleParameters():
+    return {"interval": {"descr": "Base interval of the threads in microseconds",
+                         "default": 100,
+                         "metavar": "INTV_US"},
+            "buckets":  {"descr": "Histogram width",
+                         "default": 2000,
+                         "metavar": "NUM"},
+            "priority": {"descr": "Run cyclictest with the given priority",
+                         "default": 95,
+                         "metavar": "PRIO"},
+            "breaktrace": {"descr": "Send a break trace command when latency > USEC",
+                           "default": None,
+                           "metavar": "USEC"}
+            }
+
+
+
+def create(params, logger):
+    return Cyclictest(params, logger)
+
+
+if __name__ == '__main__':
+    from rteval.rtevalConfig import rtevalConfig
+
+    l = Log()
+    l.SetLogVerbosity(Log.INFO|Log.DEBUG|Log.ERR|Log.WARN)
+
+    cfg = rtevalConfig({}, logger=l)
+    prms = {}
+    modprms = ModuleParameters()
+    for c, p in modprms.items():
+        prms[c] = p['default']
+    cfg.AppendConfig('cyclictest', prms)
+
+    cfg_ct = cfg.GetSection('cyclictest')
+    cfg_ct.reportdir = "."
+    cfg_ct.buckets = 200
+    # cfg_ct.breaktrace = 30
+
+    runtime = 10
+
+    c = Cyclictest(cfg_ct, l)
+    c._WorkloadSetup()
+    c._WorkloadPrepare()
+    c._WorkloadTask()
+    time.sleep(runtime)
+    c._WorkloadCleanup()
+    rep_n = c.MakeReport()
+
+    xml = libxml2.newDoc('1.0')
+    xml.setRootElement(rep_n)
+    xml.saveFormatFileEnc('-','UTF-8',1)
diff --git a/rteval/modules/measurement/sysstat.py b/rteval/modules/measurement/sysstat.py
new file mode 100644
index 0000000..033e882
--- /dev/null
+++ b/rteval/modules/measurement/sysstat.py
@@ -0,0 +1,157 @@
+#
+#   sysstat.py - rteval measurment module collecting system statistics
+#                using the sysstat utility
+#
+#   Copyright 2013          David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import os, sys, libxml2, tempfile, time, subprocess, base64, bz2, textwrap
+from rteval.Log import Log
+from rteval.modules import rtevalModulePrototype
+
+
+class sysstat(rtevalModulePrototype):
+    def __init__(self, config, logger=None):
+        rtevalModulePrototype.__init__(self, 'measurement', 'sysstat', logger)
+        self.__cfg      = config
+        self.__started  = False
+        self.__logentry = 0
+        self.__bin_sadc = "/usr/lib64/sa/sadc" # FIXME: Do dynamically
+        self.__datadir  =  os.path.join(self.__cfg.reportdir, 'sysstat')
+        self.__datafile = os.path.join(self.__datadir, "sysstat.dat")
+
+
+    def _WorkloadSetup(self):
+        # Nothing to do here for sysstat
+        pass
+
+
+    def _WorkloadBuild(self):
+        # Nothing to build
+        self._setReady()
+
+
+    def _WorkloadPrepare(self):
+        os.mkdir(self.__datadir)
+
+
+    def _WorkloadTask(self):
+        # This workload will actually not run any process, but
+        # it will update the data files each time rteval checks
+        # if this workload is alive.
+        #
+        # Just add a single notification that rteval started
+        if self.__logentry == 0:
+            cmd = [self.__bin_sadc, "-S", "XALL", "-C", "rteval started", self.__datafile]
+            subprocess.call(cmd)
+            self.__logentry += 1
+
+
+    def WorkloadAlive(self):
+        # Here the sysstat tool will be called, which will update
+        # the file containing the system information
+        cmd = [self.__bin_sadc, "-S", "XALL", "1", "1", self.__datafile]
+        subprocess.call(cmd)
+        self.__logentry += 1
+        return True
+
+
+    def _WorkloadCleanup(self):
+        # Add 'rteval stopped' comment line
+        cmd = [self.__bin_sadc, "-S", "XALL", "-C", "rteval stopped", self.__datafile]
+        subprocess.call(cmd)
+        self.__logentry += 1
+        self._setFinished()
+
+
+    def MakeReport(self):
+        rep_n = libxml2.newNode('sysstat')
+        rep_n.newProp('command_line', '(sysstat specifics)')
+        rep_n.newProp('num_entries', str(self.__logentry))
+
+        fp = open(self.__datafile, "rb")
+        compr = bz2.BZ2Compressor(9)
+        cmpr = compr.compress(fp.read())
+        data = base64.b64encode(cmpr + compr.flush())
+        data_n = rep_n.newTextChild(None, 'data', "\n"+"\n".join(textwrap.wrap(data,75))+"\n")
+        data_n.newProp('contents', 'sysstat/sar binary data')
+        data_n.newProp('encoding','base64')
+        data_n.newProp('compression','bz2')
+        fp.close()
+        del cmpr
+        del compr
+
+        # Return the report
+        return rep_n
+
+
+
+def ModuleInfo():
+    # sysstat features - run in parallel with outher measurement modules with loads
+    return {"parallel": True,
+            "loads": True}
+
+
+
+def ModuleParameters():
+    return {}  # No arguments available
+
+
+
+def create(params, logger):
+    return sysstat(params, logger)
+
+
+if __name__ == '__main__':
+    from rteval.rtevalConfig import rtevalConfig
+
+    l = Log()
+    l.SetLogVerbosity(Log.INFO|Log.DEBUG|Log.ERR|Log.WARN)
+
+    cfg = rtevalConfig({}, logger=l)
+    prms = {}
+    modprms = ModuleParameters()
+    for c, p in modprms.items():
+        prms[c] = p['default']
+    cfg.AppendConfig('MeasurementModuleTemplate', prms)
+
+    cfg_ct = cfg.GetSection('MeasurementModuleTemplate')
+    cfg_ct.reportdir = "."
+
+    runtime = 10
+
+    c = sysstat(cfg_ct, l)
+    c._WorkloadSetup()
+    c._WorkloadPrepare()
+    c._WorkloadTask()
+    print "Running for approx %i seconds" % runtime
+    while runtime > 0:
+        c.WorkloadAlive()
+        time.sleep(1)
+        runtime -= 1
+    c._WorkloadCleanup()
+    rep_n = c.MakeReport()
+
+    xml = libxml2.newDoc('1.0')
+    xml.setRootElement(rep_n)
+    xml.saveFormatFileEnc('-','UTF-8',1)
diff --git a/rteval/rtevalConfig.py b/rteval/rtevalConfig.py
index d23a509..16ac2d0 100644
--- a/rteval/rtevalConfig.py
+++ b/rteval/rtevalConfig.py
@@ -30,41 +30,124 @@
 #   including keys needed to generate an equivalently functional executable
 #   are deemed to be part of the source code.
 #
-import os
+import os, sys
 import ConfigParser
+from Log import Log
+from systopology import SysTopology
+
+def get_user_name():
+    name = os.getenv('SUDO_USER')
+    if not name:
+        name = os.getenv('USER')
+    if not name:
+        import pwd
+        name = pwd.getpwuid(os.getuid()).pw_name
+    if not name:
+        name = ""
+    return name
+
+def default_config_search(relative_path, verifdef=os.path.isdir):
+    ConfigDirectories=[
+        os.path.join(os.path.expanduser("~" + get_user_name()), '.rteval'),
+        '/etc/rteval',
+        '/usr/share/rteval'
+    ]
+
+    if os.path.dirname(os.path.abspath(__file__)) != '/usr/share/rteval':
+        ConfigDirectories = [
+                os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
+                os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'rteval')
+                ] + ConfigDirectories
+
+        for path in ConfigDirectories:
+            if verifdef(os.path.join(path, *relative_path)):
+                return os.path.join(path, *relative_path)
+
+    return False
+
+
+# HACK: A temporary hack to try to figure out where the install dir is.
+typical_install_paths = ('/usr/bin','/usr/local/bin')
+try:
+    if typical_install_paths.index(os.path.dirname(os.path.abspath(sys.argv[0]))):
+        installdir = os.path.dirname(os.path.abspath(sys.argv[0]))
+    else:
+        installdir = '/usr/share/rteval'
+
+except ValueError:
+    installdir = '/usr/share/rteval'
+
+default_config = {
+    'rteval': {
+        'quiet'      : False,
+        'verbose'    : False,
+        'keepdata'   : True,
+        'debugging'  : False,
+        'duration'   : '60',
+        'sysreport'  : False,
+        'reportdir'  : None,
+        'reportfile' : None,
+        'workdir'    : os.getcwd(),
+        'installdir' : installdir,
+        'srcdir'     : default_config_search(['loadsource']),
+        'xmlrpc'     : None,
+        'xslt_report': default_config_search(['rteval_text.xsl'], os.path.isfile),
+        'report_interval': '600',
+        'logging'    : False,
+        }
+    }
+
 
 class rtevalCfgSection(object):
-
     def __init__(self, section_cfg):
-        self.__update_config_vars(section_cfg)
-        self.__iter_list = None
+        if type(section_cfg) is not dict:
+            raise TypeError('section_cfg argument is not a dict variable')
+
+        self.__dict__['_rtevalCfgSection__cfgdata'] = section_cfg
+        self.__dict__['_rtevalCfgSection__iter_list'] = None
+
 
     def __str__(self):
         "Simple method for dumping config when object is used as a string"
-        return str(self.__cfgdata)
+        if len(self.__cfgdata) == 0:
+            return "# empty"
+        return "\n".join(["%s: %s" % (k,v) for k,v in self.__cfgdata.items()]) + "\n"
 
-    def __update_config_vars(self, section_cfg):
-        if section_cfg is None:
-            return
-        self.__cfgdata = section_cfg
 
-        # create member variables from config info
-        for m in section_cfg.keys():
-            self.__dict__[m] = section_cfg[m]
+    def __setattr__(self, key, val):
+        self.__cfgdata[key] = val
+
+
+    def __getattr__(self, key):
+        if key in self.__cfgdata.keys():
+            return self.__cfgdata[key]
+        return None
+
+
+    def items(self):
+        return self.__cfgdata.items()
 
 
     def __iter__(self):
-        "Initialises for an iterator loop"
-        self.__iter_list = self.keys()
+        "Initialises the iterator loop"
+        self.__dict__['_rtevalCfgSection__iter_list'] = self.__cfgdata.keys()
         return self
 
 
     def next(self):
         "Function used by the iterator"
-        if len(self.__iter_list) == 0:
+
+        if not self.__dict__['_rtevalCfgSection__iter_list'] \
+                or len(self.__dict__['_rtevalCfgSection__iter_list']) == 0:
             raise StopIteration
         else:
-            elmt = self.__iter_list.pop()
+            elmt = self.__dict__['_rtevalCfgSection__iter_list'].pop()
+
+            # HACK: This element shouldn't really appear here ... why!??!
+            while (elmt == '_rtevalCfgSection__cfgdata') and \
+                    (len(self.__dict__['_rtevalCfgSection__iter_list']) > 0):
+                elmt = self.__dict__['_rtevalCfgSection__iter_list'].pop()
+
             return (elmt, self.__cfgdata[elmt])
 
 
@@ -77,37 +160,72 @@
         "keys() wrapper for configuration data"
         return self.__cfgdata.keys()
 
-    def setdefault(self, key, defvalue):
-        if not self.__dict__.has_key(key):
-            self.__dict__[key] = defvalue
-        return self.__dict__[key]
 
-class rtevalConfig(rtevalCfgSection):
+    def setdefault(self, key, defvalue):
+        if not self.__cfgdata.has_key(key):
+            self.__cfgdata[key] = defvalue
+        return self.__cfgdata[key]
+
+
+    def update(self, newdict):
+        if type(newdict) is not dict:
+            raise TypeError('update() method expects a dict as argument')
+
+        for key, val in newdict.iteritems():
+            self.__cfgdata[key] = val
+
+
+    def wipe(self):
+        self.__cfgdata = {}
+
+
+
+class rtevalConfig(object):
     "Config parser for rteval"
 
-    def __init__(self, initvars = None, logfunc = None):
-        self.__config_data = initvars or {}
+    def __init__(self, initvars = None, logger = None):
+        self.__config_data = {}
         self.__config_files = []
+        self.__logger = logger
 
-        # export the rteval section to member variables, if section is found
-        try:
-            self._rtevalCfgSection__update_config_vars(self.__config_data['rteval'])
-        except KeyError:
-            pass  # If 'rteval' is not found, KeyError is raised and that's okay to ignore
-        except Exception, err:
-            raise err # All other errors will be passed on
+        # get our system topology info
+        self.__systopology = SysTopology()
+        print("got system topology: %s" % self.__systopology)
 
-        self.__info = logfunc or self.__nolog
+        # Import the default config first
+        for sect, vals in default_config.items():
+            self.__update_section(sect, vals)
+
+        # Set the runtime provided init variables
+        if initvars:
+            if type(initvars) is not dict:
+                raise TypeError('initvars argument is not a dict variable')
+
+            for sect, vals in initvars.items():
+                self.__update_section(sect, vals)
+
+
+    def __update_section(self, section, newvars):
+        if not section or not newvars:
+            return
+
+        if not self.__config_data.has_key(section):
+            self.__config_data[section] = rtevalCfgSection(newvars)
+        else:
+            self.__config_data[section].update(newvars)
 
 
     def __str__(self):
         "Simple method for dumping config when object is used as a string"
-        return str(self.__config_data)
+        ret = ""
+        for sect in self.__config_data.keys():
+            ret += "[%s]\n%s\n" % (sect, str(self.__config_data[sect]))
+        return ret
 
 
-    def __nolog(self, str):
-        "Dummy log function, used when no log function is configured"
-        pass
+    def __info(self, str):
+        if self.__logger:
+            self.__logger.log(Log.INFO, str)
 
 
     def __find_config(self):
@@ -136,51 +254,57 @@
 
         self.__info("reading config file %s" % cfgfile)
         ini = ConfigParser.ConfigParser()
+        ini.optionxform = str
         ini.read(cfgfile)
 
-        # wipe any previously read config info (other than the rteval stuff)
+        # wipe any previously read config info
         if not append:
             for s in self.__config_data.keys():
-                if s == 'rteval':
-                    continue
-                self.__config_data[s] = {}
+                self.__config_data[s].wipe()
 
         # copy the section data into the __config_data dictionary
         for s in ini.sections():
-            if not self.__config_data.has_key(s):
-                self.__config_data[s] = {}
-            for i in ini.items(s):
-                self.__config_data[s][i[0]] = i[1]
+            cfg = {}
+            for (k,v) in ini.items(s):
+                cfg[k] = v.split('#')[0].strip()
 
-        # export the rteval section to member variables
-        try:
-            self._rtevalCfgSection__update_config_vars(self.__config_data['rteval'])
-        except KeyError:
-            pass
-        except Exception, err:
-            raise err
+            self.__update_section(s, cfg)
+
 
         # Register the file as read
         self.__config_files.append(cfgfile)
         return cfgfile
 
+
     def ConfigParsed(self, fname):
         "Returns True if the config file given by name has already been parsed"
         return self.__config_files.__contains__(fname)
 
 
+    def UpdateFromOptionParser(self, parser):
+        "Parse through the command line options and update the appropriate config settings"
+
+        last_sect = None
+        for sk,v in sorted(vars(parser.values).items()):
+            # optparse key template: {sectionname}___{key}
+            k = sk.split('___')
+            if k[0] != last_sect:
+                # If the section name changed, retrieve the section variables
+                try:
+                    sect = self.GetSection(k[0])
+                except KeyError:
+                    # If section does not exist, create it
+                    self.AppendConfig(k[0], {k[1]: v})
+                    sect = self.GetSection(k[0])
+
+                last_sect = k[0]
+
+            setattr(sect, k[1], v)
+
+
     def AppendConfig(self, section, cfgvars):
-        "Add more config parameters to a section.  cfgvard must be a dictionary of parameters"
-
-        if type(cfgvars) is dict:
-            for o in cfgvars.keys():
-                self.__config_data[section][o] = cfgvars[o]
-        else:
-            for o in cfgvars.__dict__.keys():
-                self.__config_data[section][o] = cfgvars.__dict__[o]
-
-        if section == 'rteval':
-            self._rtevalCfgSection__update_config_vars(self.__config_data['rteval'])
+        "Add more config parameters to a section.  cfgvars must be a dictionary of parameters"
+        self.__update_section(section, cfgvars)
 
 
     def HasSection(self, section):
@@ -190,15 +314,17 @@
     def GetSection(self, section):
         try:
             # Return a new object with config settings of a given section
-            return rtevalCfgSection(self.__config_data[section])
+            return self.__config_data[section]
         except KeyError, err:
             raise KeyError("The section '%s' does not exist in the config file" % section)
 
 
 def unit_test(rootdir):
     try:
-        cfg = rtevalConfig()
-        cfg.Load(rootdir + '/rteval/rteval.conf')
+        l = Log()
+        l.SetLogVerbosity(Log.INFO)
+        cfg = rtevalConfig(logger=l)
+        cfg.Load(os.path.join(rootdir, 'rteval.conf'))
         print cfg
         return 0
     except Exception, e:
diff --git a/rteval/rtevalMailer.py b/rteval/rtevalMailer.py
index 7b0d5f4..4588f14 100644
--- a/rteval/rtevalMailer.py
+++ b/rteval/rtevalMailer.py
@@ -1,7 +1,7 @@
 #
 #   rtevalmailer.py - module for sending e-mails
 #
-#   Copyright 2009,2010   David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -13,9 +13,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/rteval/rtevalReport.py b/rteval/rtevalReport.py
new file mode 100644
index 0000000..b22fa39
--- /dev/null
+++ b/rteval/rtevalReport.py
@@ -0,0 +1,141 @@
+#  rtevalReport.py - Takes care of the report generation
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import os, tarfile
+from datetime import datetime
+import xmlout
+
+
+class rtevalReport(object):
+    def __init__(self, rtev_version, installdir, annotate):
+        self.__version = rtev_version
+        self.__installdir = installdir
+        self.__annotate = annotate
+        self.__start = datetime.now()
+        self.__xmlreport = None
+        self.__reportdir = None
+        self.__xmlfname = None
+
+
+    def _report(self, measure_start, xslt_tpl):
+        "Create a screen report, based on a predefined XSLT template"
+
+        if measure_start is None:
+            raise Exception("No measurement runs have been attempted")
+
+        duration = datetime.now() - measure_start
+        seconds = duration.seconds
+        hours = seconds / 3600
+        if hours: seconds -= (hours * 3600)
+        minutes = seconds / 60
+        if minutes: seconds -= (minutes * 60)
+
+        # Start new XML report
+        self.__xmlreport = xmlout.XMLOut('rteval', self.__version)
+        self.__xmlreport.NewReport()
+
+        self.__xmlreport.openblock('run_info', {'days': duration.days,
+                                 'hours': hours,
+                                 'minutes': minutes,
+                                 'seconds': seconds})
+        self.__xmlreport.taggedvalue('date', self.__start.strftime('%Y-%m-%d'))
+        self.__xmlreport.taggedvalue('time', self.__start.strftime('%H:%M:%S'))
+        if self.__annotate:
+            self.__xmlreport.taggedvalue('annotate', self.__annotate)
+        self.__xmlreport.closeblock()
+
+        # Collect and add info about the system
+        self.__xmlreport.AppendXMLnodes(self._sysinfo.MakeReport())
+
+        # Add load info
+        self.__xmlreport.AppendXMLnodes(self._loadmods.MakeReport())
+
+        # Add measurement data
+        self.__xmlreport.AppendXMLnodes(self._measuremods.MakeReport())
+
+        # Close the report - prepare for return the result
+        self.__xmlreport.close()
+
+        # Write the XML to the report directory
+        if self.__xmlfname != None:
+            self.__xmlreport.Write(self.__xmlfname, None)
+
+        # Write a text report to stdout as well, using the
+        # rteval_text.xsl template
+        self.__xmlreport.Write("-", xslt_tpl)
+
+
+    def GetXMLreport(self):
+        "Retrieves the complete rteval XML report as a libxml2.xmlDoc object"
+        return self.__xmlreport.GetXMLdocument()
+
+
+    def _show_report(self, xmlfile, xsltfile):
+        '''summarize a previously generated xml file'''
+        print "Loading %s for summarizing" % xmlfile
+
+        xsltfullpath = os.path.join(self.__installdir, xsltfile)
+        if not os.path.exists(xsltfullpath):
+            raise RuntimeError, "can't find XSL template (%s)!" % xsltfullpath
+
+        xmlreport = xmlout.XMLOut('rteval', self.__version)
+        xmlreport.LoadReport(xmlfile)
+        xmlreport.Write('-', xsltfullpath)
+        del xmlreport
+
+
+    def _make_report_dir(self, workdir, reportfile):
+        t = self.__start
+        i = 1
+        self.__reportdir = os.path.join(workdir,
+                                      t.strftime("rteval-%Y%m%d-"+str(i)))
+        while os.path.exists(self.__reportdir):
+            i += 1
+            self.__reportdir = os.path.join(workdir,
+                                          t.strftime('rteval-%Y%m%d-'+str(i)))
+        if not os.path.isdir(self.__reportdir):
+            os.mkdir(self.__reportdir)
+            os.mkdir(os.path.join(self.__reportdir, "logs"))
+
+        self.__xmlfname = os.path.join(self.__reportdir, reportfile)
+        return self.__reportdir
+
+
+    def _tar_results(self):
+        if not os.path.isdir(self.__reportdir):
+            raise RuntimeError, "no such directory: %s" % reportdir
+
+        dirname = os.path.dirname(self.__reportdir)
+        rptdir = os.path.basename(self.__reportdir)
+        cwd = os.getcwd()
+        os.chdir(dirname)
+        try:
+            t = tarfile.open(rptdir + ".tar.bz2", "w:bz2")
+            t.add(rptdir)
+            t.close()
+        except:
+            os.chdir(cwd)
+
diff --git a/rteval/rtevalXMLRPC.py b/rteval/rtevalXMLRPC.py
new file mode 100644
index 0000000..ed080cb
--- /dev/null
+++ b/rteval/rtevalXMLRPC.py
@@ -0,0 +1,125 @@
+# rtevalXMLRPC.py - main rteval XML-RPC class
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import socket, time
+import rtevalclient, xmlrpclib
+from Log import Log
+
+class rtevalXMLRPC(object):
+    def __init__(self, host, logger, mailer = None):
+        self.__host = host
+        self.__url= "http://%s/rteval/API1/" % self.__host
+        self.__logger = logger
+        self.__mailer = mailer
+        self.__client = rtevalclient.rtevalclient(self.__url)
+
+
+    def Ping(self):
+        res = None
+        self.__logger.log(Log.DEBUG, "Checking if XML-RPC server '%s' is reachable" % self.__host)
+        attempt = 0
+        ping_success = False
+        warning_sent = False
+        while attempt < 6:
+            try:
+                res = self.__client.Hello()
+                attempt = 10
+                ping_success = True
+            except xmlrpclib.ProtocolError:
+                # Server do not support Hello(), but is reachable
+                self.__logger.log(Log.INFO, "Got XML-RPC connection with %s but it did not support Hello()"
+                                  % self.__host)
+                res = None
+            except socket.error, err:
+                self.__logger.log(Log.INFO, "Could not establish XML-RPC contact with %s\n%s"
+                                  % (self.__host, str(err)))
+
+                # Do attempts handling
+                attempt += 1
+                if attempt > 5:
+                    break # To avoid sleeping before we abort
+
+                if (self.__mailer is not None) and (not warning_sent):
+                    self.__mailer.SendMessage("[RTEVAL:WARNING] Failed to ping XML-RPC server",
+                                            "Server %s did not respond." % self.__host)
+                    warning_sent = True
+
+                print "Failed pinging XML-RPC server.  Doing another attempt(%i) " % attempt
+                time.sleep(attempt) #*15) # Incremental sleep - sleep attempts*15 seconds
+                ping_success = False
+
+        if res:
+            self.__logger.log(Log.INFO, "Verified XML-RPC connection with %s (XML-RPC API version: %i)"
+                              % (res["server"], res["APIversion"]))
+            self.__logger.log(Log.DEBUG, "Recieved greeting: %s" % res["greeting"])
+
+        return ping_success
+
+
+    def SendReport(self, xmlreport):
+        "Sends the report to a given XML-RPC host.  Returns 0 on success or 2 on submission failure."
+
+        attempt = 0
+        exitcode = 2   # Presume failure
+        warning_sent = False
+        while attempt < 6:
+            try:
+                print "Submitting report to %s" % self.__url
+                rterid = self.__client.SendReport(xmlreport)
+                print "Report registered with submission id %i" % rterid
+                attempt = 10
+                exitcode = 0 # Success
+            except socket.error:
+                attempt += 1
+                if attempt > 5:
+                    break # To avoid sleeping before we abort
+
+                if (self.__mailer is not None) and (not warning_sent):
+                    self.__mailer.SendMessage("[RTEVAL:WARNING] Failed to submit report to XML-RPC server",
+                                              "Server %s did not respond.  Not giving up yet."
+                                              % self.__host)
+                    warning_sent = True
+
+                print "Failed sending report.  Doing another attempt(%i) " % attempt
+                time.sleep(attempt) #*5*60) # Incremental sleep - sleep attempts*5 minutes
+
+            except Exception, err:
+                raise err
+
+
+        if (self.__mailer is not None):
+            # Send final result messages
+            if exitcode == 2:
+                self.__mailer.SendMessage("[RTEVAL:FAILURE] Failed to submit report to XML-RPC server",
+                                          "Server %s did not respond at all after %i attempts."
+                                          % (self.__host, attempt - 1))
+            elif (exitcode == 0) and warning_sent:
+                self.__mailer.SendMessage("[RTEVAL:SUCCESS] XML-RPC server available again",
+                                          "Succeeded to submit the report to %s"
+                                          % self.__host)
+
+        return exitcode
+
diff --git a/rteval/rteval_histogram_raw.xsl b/rteval/rteval_histogram_raw.xsl
index 69399bc..9aadcc3 100644
--- a/rteval/rteval_histogram_raw.xsl
+++ b/rteval/rteval_histogram_raw.xsl
@@ -10,15 +10,15 @@
     <xsl:text>core&#09;index&#09;value&#10;</xsl:text>
 
     <!-- Extract overall system histogram data -->
-    <xsl:apply-templates select="cyclictest/system/histogram/bucket">
+    <xsl:apply-templates select="Measurements/Profile/cyclictest/system/histogram/bucket">
       <xsl:with-param name="label" select="'system'"/> 
-      <xsl:sort select="cyclictest/core/histogram/bucket/@index" data-type="number"/>
+      <xsl:sort select="Measurements/Profile/cyclictest/core/histogram/bucket/@index" data-type="number"/>
     </xsl:apply-templates>
 
     <!-- Extract per cpu core histogram data -->
-    <xsl:apply-templates select="cyclictest/core/histogram/bucket">
-      <xsl:sort select="cyclictest/core/@id" data-type="number"/>
-      <xsl:sort select="cyclictest/core/histogram/bucket/@index" data-type="number"/>
+    <xsl:apply-templates select="Measurements/Profile/cyclictest/core/histogram/bucket">
+      <xsl:sort select="Measurements/Profile/cyclictest/core/@id" data-type="number"/>
+      <xsl:sort select="Measurements/Profile/cyclictest/core/histogram/bucket/@index" data-type="number"/>
     </xsl:apply-templates>
   </xsl:template>
   <!--                              -->
@@ -26,7 +26,7 @@
   <!--                              -->
 
   <!-- Record formatting -->
-  <xsl:template match="cyclictest/system/histogram/bucket|cyclictest/core/histogram/bucket">
+  <xsl:template match="/rteval/Measurements/Profile/cyclictest/*/histogram/bucket">
     <xsl:param name="label"/>
     <xsl:choose>
       <!-- If we don't have a id tag in what should be a 'core' tag, use the given label -->
diff --git a/rteval/rteval_text.xsl b/rteval/rteval_text.xsl
index 2a99586..e99c016 100644
--- a/rteval/rteval_text.xsl
+++ b/rteval/rteval_text.xsl
@@ -26,29 +26,35 @@
     <xsl:text>&#10;&#10;</xsl:text>
 
     <xsl:text>   Tested node:  </xsl:text>
-    <xsl:value-of select="uname/node"/>
+    <xsl:value-of select="SystemInfo/uname/node|uname/node"/>
     <xsl:text>&#10;</xsl:text>
 
     <xsl:text>   Model:        </xsl:text>
-    <xsl:value-of select="HardwareInfo/GeneralInfo/Manufacturer"/>
-    <xsl:text> - </xsl:text><xsl:value-of select="HardwareInfo/GeneralInfo/ProductName"/>
+    <xsl:value-of select="SystemInfo/DMIinfo/HardwareInfo/GeneralInfo/Manufacturer|HardwareInfo/GeneralInfo/ProductName"/>
+    <xsl:text> - </xsl:text><xsl:value-of select="SystemInfo/DMIinfo/HardwareInfo/GeneralInfo/ProductName|HardwareInfo/GeneralInfo/ProductName"/>
     <xsl:text>&#10;</xsl:text>
 
     <xsl:text>   BIOS version: </xsl:text>
-    <xsl:value-of select="HardwareInfo/BIOS"/>
+    <xsl:value-of select="SystemInfo/DMIinfo/HardwareInfo/BIOS|HardwareInfo/BIOS"/>
     <xsl:text> (ver: </xsl:text>
-    <xsl:value-of select="HardwareInfo/BIOS/@Version"/>
+    <xsl:value-of select="SystemInfo/DMIinfo/HardwareInfo/BIOS/@Version|HardwareInfo/BIOS/@Version"/>
     <xsl:text>, rev :</xsl:text>
-    <xsl:value-of select="HardwareInfo/BIOS/@BIOSrevision"/>
+    <xsl:value-of select="SystemInfo/DMIinfo/HardwareInfo/BIOS/@BIOSrevision|HardwareInfo/BIOS/@BIOSrevision"/>
     <xsl:text>, release date: </xsl:text>
-    <xsl:value-of select="HardwareInfo/BIOS/@ReleaseDate"/>
+    <xsl:value-of select="SystemInfo/DMIinfo/HardwareInfo/BIOS/@ReleaseDate|HardwareInfo/BIOS/@ReleaseDate"/>
     <xsl:text>)</xsl:text>
     <xsl:text>&#10;&#10;</xsl:text>
 
     <xsl:text>   CPU cores:    </xsl:text>
     <xsl:choose>
+      <xsl:when test="SystemInfo/CPUtopology">
+	<xsl:value-of select="SystemInfo/CPUtopology/@num_cpu_cores"/>
+	<xsl:text> (online: </xsl:text>
+	<xsl:value-of select="SystemInfo/CPUtopology/@num_cpu_cores_online"/>
+	<xsl:text>)</xsl:text>
+      </xsl:when>
       <xsl:when test="hardware/cpu_topology">
-	<xsl:value-of select="hardware/cpu_topology/@num_cpu_cores"/>
+        <xsl:value-of select="hardware/cpu_topology/@num_cpu_cores"/>
 	<xsl:text> (online: </xsl:text>
 	<xsl:value-of select="hardware/cpu_topology/@num_cpu_cores_online"/>
 	<xsl:text>)</xsl:text>
@@ -60,15 +66,24 @@
     </xsl:choose>
     <xsl:text>&#10;</xsl:text>
 
-    <xsl:if test="hardware/numa_nodes">
-      <xsl:text>   NUMA Nodes:   </xsl:text>
-      <xsl:value-of select="hardware/numa_nodes"/>
-      <xsl:text>&#10;</xsl:text>
-    </xsl:if>
+    <xsl:text>   NUMA Nodes:   </xsl:text>
+    <xsl:choose>
+      <xsl:when test="SystemInfo/Memory/numa_nodes">
+        <xsl:value-of select="SystemInfo/Memory/numa_nodes"/>
+      </xsl:when>
+      <xsl:when test="hardware/numa_nodes">
+        <xsl:value-of select="hardware/numa_nodes"/>
+      </xsl:when>
+      <xsl:otherwise>(unknown)</xsl:otherwise>
+    </xsl:choose>
+    <xsl:text>&#10;</xsl:text>
 
     <xsl:text>   Memory:       </xsl:text>
-    <xsl:value-of select="hardware/memory_size"/>
+    <xsl:value-of select="SystemInfo/Memory/memory_size|hardware/memory_size"/>
     <xsl:choose>
+      <xsl:when test="SystemInfo/Memory/memory_size/@unit">
+	<xsl:value-of select="concat(' ',SystemInfo/Memory/memory_size/@unit)"/>
+      </xsl:when>
       <xsl:when test="hardware/memory_size/@unit">
 	<xsl:value-of select="concat(' ',hardware/memory_size/@unit)"/>
       </xsl:when>
@@ -79,42 +94,50 @@
     <xsl:text>&#10;</xsl:text>
 
     <xsl:text>   Kernel:       </xsl:text>
-    <xsl:value-of select="uname/kernel"/>
-    <xsl:if test="uname/kernel/@is_RT = '1'">  (RT enabled)</xsl:if>
+    <xsl:value-of select="SystemInfo/uname/kernel|uname/kernel"/>
+    <xsl:if test="SystemInfo/uname/kernel/@is_RT = '1' or uname/kernel/@is_RT = '1'">  (RT enabled)</xsl:if>
     <xsl:text>&#10;</xsl:text>
 
     <xsl:text>   Base OS:      </xsl:text>
-    <xsl:value-of select="uname/baseos"/>
+    <xsl:value-of select="SystemInfo/uname/baseos|uname/baseos"/>
     <xsl:text>&#10;</xsl:text>
 
     <xsl:text>   Architecture: </xsl:text>
-    <xsl:value-of select="uname/arch"/>
+    <xsl:value-of select="SystemInfo/uname/arch|uname/arch"/>
     <xsl:text>&#10;</xsl:text>
 
     <xsl:text>   Clocksource:  </xsl:text>
-    <xsl:value-of select="clocksource/current"/>
+    <xsl:value-of select="SystemInfo/Kernel/ClockSource/source[@current='1']|clocksource/current"/>
     <xsl:text>&#10;</xsl:text>
 
     <xsl:text>   Available:    </xsl:text>
-    <xsl:value-of select="clocksource/available"/>
+    <xsl:choose>
+      <xsl:when test="SystemInfo/Kernel/ClockSource/source">
+        <xsl:for-each select="SystemInfo/Kernel/ClockSource/source">
+          <xsl:value-of select="."/>
+          <xsl:text> </xsl:text>
+        </xsl:for-each>
+      </xsl:when>
+      <xsl:when test="clocksource/available">
+        <xsl:value-of select="clocksource/available"/>
+      </xsl:when>
+      <xsl:otherwise>(unknown)</xsl:otherwise>
+    </xsl:choose>
     <xsl:text>&#10;&#10;</xsl:text>
    
-    <xsl:text>   Load commands:&#10;</xsl:text>
+    <xsl:text>   System load:&#10;</xsl:text>
     <xsl:text>       Load average: </xsl:text>
     <xsl:value-of select="loads/@load_average"/>
     <xsl:text>&#10;</xsl:text>
 
-    <xsl:text>       Commands:&#10;</xsl:text>
-    <xsl:apply-templates select="loads/command_line"/>
+    <xsl:if test="loads/command_line">
+      <xsl:text>&#10;</xsl:text>
+      <xsl:text>       Executed loads:&#10;</xsl:text>
+      <xsl:apply-templates select="loads/command_line"/>
+    </xsl:if>
     <xsl:text>&#10;</xsl:text>
-
-   <!-- Format other sections of the report, if they are found                 -->
-   <!-- To add support for even more sections, just add them into the existing -->
-   <!-- xsl:apply-tempaltes tag, separated with pipe(|)                        -->
-   <!--                                                                        -->
-   <!--       select="cyclictest|new_foo_section|another_section"              -->
-   <!--                                                                        -->
-   <xsl:apply-templates select="cyclictest"/>
+    <!-- Generate a summary report for all measurement profiles -->
+    <xsl:apply-templates select="Measurements/Profile"/>
    <xsl:text>  ===================================================================&#10;</xsl:text>
 </xsl:template>
   <!--                              -->
@@ -137,19 +160,56 @@
   </xsl:template>
 
 
-  <!-- Format the cyclic test section of the report -->
-  <xsl:template match="/rteval/cyclictest">
-    <xsl:text>   Latency test&#10;</xsl:text>
+  <xsl:template match="/rteval/Measurements/Profile">
+    <xsl:text>   Measurement profile </xsl:text>
+    <xsl:value-of select="position()"/><xsl:text>: </xsl:text>
+    <xsl:choose>
+      <xsl:when test="@loads = '1'"><xsl:text>With loads, </xsl:text></xsl:when>
+      <xsl:otherwise><xsl:text>Without loads, </xsl:text></xsl:otherwise>
+    </xsl:choose>
+    <xsl:choose>
+      <xsl:when test="@parallel = '1'">
+        <xsl:text>measurements in parallel</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:text>measurements serialised</xsl:text>
+      </xsl:otherwise>
+    </xsl:choose>
+    <xsl:text>&#10;</xsl:text>
 
-    <xsl:text>      Command:</xsl:text>
-    <xsl:value-of select="command_line"/>
+    <!-- Format other sections of the report, if they are found                 -->
+    <!-- To add support for even more sections, just add them into the existing -->
+    <!-- xsl:apply-tempaltes tag, separated with pipe(|)                        -->
+    <!--                                                                        -->
+    <!--       select="cyclictest|new_foo_section|another_section"              -->
+    <!--                                                                        -->
+    <xsl:apply-templates select="cyclictest|hwlatdetect[@format='1.0']|sysstat"/>
+    <xsl:text>&#10;</xsl:text>
+  </xsl:template>
+
+  <!-- Format the cyclic test section of the report -->
+  <xsl:template match="/rteval/Measurements/Profile/cyclictest">
+    <xsl:text>       Latency test&#10;</xsl:text>
+
+    <xsl:text>          Started: </xsl:text>
+    <xsl:value-of select="timestamps/runloop_start"/>
+    <xsl:text>&#10;</xsl:text>
+
+    <xsl:text>          Stopped: </xsl:text>
+    <xsl:value-of select="timestamps/runloop_stop"/>
+    <xsl:text>&#10;</xsl:text>
+
+    <xsl:text>          Command: </xsl:text>
+    <xsl:value-of select="@command_line"/>
     <xsl:text>&#10;&#10;</xsl:text>
 
-    <xsl:text>      System:  </xsl:text>
+    <xsl:apply-templates select="abort_report"/>
+
+    <xsl:text>          System:  </xsl:text>
     <xsl:value-of select="system/@description"/>
     <xsl:text>&#10;</xsl:text>
 
-    <xsl:text>      Statistics: &#10;</xsl:text>
+    <xsl:text>          Statistics: &#10;</xsl:text>
     <xsl:apply-templates select="system/statistics"/>
 
     <!-- Add CPU core info and stats-->
@@ -160,69 +220,141 @@
 
 
   <!--  Format the CPU core section in the cyclict test part -->
-  <xsl:template match="cyclictest/core">
-    <xsl:text>      CPU core </xsl:text>
+  <xsl:template match="/rteval/Measurements/Profile/cyclictest/core">
+    <xsl:text>          CPU core </xsl:text>
     <xsl:value-of select="@id"/>
-    <xsl:text>   Priority: </xsl:text>
+    <xsl:text>       Priority: </xsl:text>
     <xsl:value-of select="@priority"/>
     <xsl:text>&#10;</xsl:text>
-    <xsl:text>      Statistics: </xsl:text>
+    <xsl:text>          Statistics: </xsl:text>
     <xsl:text>&#10;</xsl:text>
     <xsl:apply-templates select="statistics"/>
   </xsl:template>
 
 
   <!-- Generic formatting of statistics information -->
-  <xsl:template match="statistics">
-    <xsl:text>          Samples:           </xsl:text>
+  <xsl:template match="/rteval/Measurements/Profile/cyclictest/*/statistics">
+    <xsl:text>            Samples:           </xsl:text>
     <xsl:value-of select="samples"/>
     <xsl:text>&#10;</xsl:text>
 
-    <xsl:text>          Mean:              </xsl:text>
-    <xsl:value-of select="mean"/>
-    <xsl:value-of select="mean/@unit"/>
+    <xsl:if test="samples > 0">
+      <xsl:text>            Mean:              </xsl:text>
+      <xsl:value-of select="mean"/>
+      <xsl:value-of select="mean/@unit"/>
+      <xsl:text>&#10;</xsl:text>
+
+      <xsl:text>            Median:            </xsl:text>
+      <xsl:value-of select="median"/>
+      <xsl:value-of select="median/@unit"/>
+      <xsl:text>&#10;</xsl:text>
+
+      <xsl:text>            Mode:              </xsl:text>
+      <xsl:value-of select="mode"/>
+      <xsl:value-of select="mode/@unit"/>
+      <xsl:text>&#10;</xsl:text>
+
+      <xsl:text>            Range:             </xsl:text>
+      <xsl:value-of select="range"/>
+      <xsl:value-of select="range/@unit"/>
+      <xsl:text>&#10;</xsl:text>
+
+      <xsl:text>            Min:               </xsl:text>
+      <xsl:value-of select="minimum"/>
+      <xsl:value-of select="minimum/@unit"/>
+      <xsl:text>&#10;</xsl:text>
+
+      <xsl:text>            Max:               </xsl:text>
+      <xsl:value-of select="maximum"/>
+      <xsl:value-of select="maximum/@unit"/>
+      <xsl:text>&#10;</xsl:text>
+
+      <xsl:text>            Mean Absolute Dev: </xsl:text>
+      <xsl:value-of select="mean_absolute_deviation"/>
+      <xsl:value-of select="mean_absolute_deviation/@unit"/>
+      <xsl:text>&#10;</xsl:text>
+
+      <xsl:text>            Std.dev:           </xsl:text>
+      <xsl:value-of select="standard_deviation"/>
+      <xsl:value-of select="standard_deviation/@unit"/>
+      <xsl:text>&#10;</xsl:text>
+    </xsl:if>
+    <xsl:text>&#10;</xsl:text>
+  </xsl:template>
+
+
+  <!-- Format the hwlatdetect test section of the report -->
+  <xsl:template match="/rteval/Measurements/Profile/hwlatdetect[@format='1.0' and not(@aborted)]">
+    <xsl:text>     Hardware latency detector&#10;</xsl:text>
+
+    <xsl:text>       Run duration: </xsl:text>
+    <xsl:value-of select="RunParams/@duration"/>
+    <xsl:text> seconds&#10;</xsl:text>
+
+    <xsl:text>       Threshold:    </xsl:text>
+    <xsl:value-of select="RunParams/@threshold"/>
+    <xsl:text>us&#10;</xsl:text>
+
+    <xsl:text>       Width:       </xsl:text>
+    <xsl:value-of select="RunParams/@width"/>
+    <xsl:text>us&#10;</xsl:text>
+
+    <xsl:text>       Window size: </xsl:text>
+    <xsl:value-of select="RunParams/@window"/>
+    <xsl:text>us&#10;&#10;</xsl:text>
+
+    <xsl:text>       Threshold exceeded </xsl:text>
+    <xsl:value-of select="samples/@count"/>
+    <xsl:text> times&#10;</xsl:text>
+    <xsl:apply-templates select="samples/sample"/>
+  </xsl:template>
+
+  <xsl:template match="/rteval/Measurements/Profile/hwlatdetect[@format='1.0' and @aborted > 0]">
+    <xsl:text>     Hardware latency detector&#10;</xsl:text>
+    <xsl:text>        ** WARNING ** hwlatedect failed to run&#10;</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="/rteval/Measurements/Profile/hwlatdetect[@format='1.0']/samples/sample">
+    <xsl:text>         - @</xsl:text>
+    <xsl:value-of select="@timestamp"/>
+    <xsl:text>  </xsl:text>
+    <xsl:value-of select="@duration"/>
+    <xsl:text>us&#10;</xsl:text>
+  </xsl:template>
+
+  <!-- Format the cyclic test section of the report -->
+  <xsl:template match="/rteval/Measurements/Profile/sysstat">
+    <xsl:text>       sysstat measurements&#10;</xsl:text>
+
+    <xsl:text>          Started: </xsl:text>
+    <xsl:value-of select="timestamps/runloop_start"/>
     <xsl:text>&#10;</xsl:text>
 
-    <xsl:text>          Median:            </xsl:text>
-    <xsl:value-of select="median"/>
-    <xsl:value-of select="median/@unit"/>
+    <xsl:text>          Stopped: </xsl:text>
+    <xsl:value-of select="timestamps/runloop_stop"/>
     <xsl:text>&#10;</xsl:text>
 
-    <xsl:text>          Mode:              </xsl:text>
-    <xsl:value-of select="mode"/>
-    <xsl:value-of select="mode/@unit"/>
+    <xsl:text>          Records saved: </xsl:text>
+    <xsl:value-of select="@num_entries"/>
     <xsl:text>&#10;</xsl:text>
+  </xsl:template>
 
-    <xsl:text>          Range:             </xsl:text>
-    <xsl:value-of select="range"/>
-    <xsl:value-of select="range/@unit"/>
-    <xsl:text>&#10;</xsl:text>
+  <!-- Format information about aborts - if present -->
+  <xsl:template match="abort_report">
+      <xsl:text>      Run aborted: </xsl:text>
+      <xsl:value-of select="@reason"/>
+      <xsl:text>&#10;</xsl:text>
 
-    <xsl:text>          Min:               </xsl:text>
-    <xsl:value-of select="minimum"/>
-    <xsl:value-of select="minimum/@unit"/>
-    <xsl:text>&#10;</xsl:text>
-
-    <xsl:text>          Max:               </xsl:text>
-    <xsl:value-of select="maximum"/>
-    <xsl:value-of select="maximum/@unit"/>
-    <xsl:text>&#10;</xsl:text>
-
-    <xsl:text>          Mean Absolute Dev: </xsl:text>
-    <xsl:value-of select="mean_absolute_deviation"/>
-    <xsl:value-of select="mean_absolute_deviation/@unit"/>
-    <xsl:text>&#10;</xsl:text>
-
-    <xsl:text>          Variance:          </xsl:text>
-    <xsl:value-of select="variance"/>
-    <xsl:value-of select="variance/@unit"/>
-    <xsl:text>&#10;</xsl:text>
-
-    <xsl:text>          Std.dev:           </xsl:text>
-    <xsl:value-of select="standard_deviation"/>
-    <xsl:value-of select="standard_deviation/@unit"/>
-    <xsl:text>&#10;&#10;</xsl:text>
-
+      <xsl:if test="breaktrace">
+        <xsl:text>                   </xsl:text>
+        <xsl:text>Aborted due to latency exceeding </xsl:text>
+        <xsl:value-of select="breaktrace/@latency_threshold"/>
+        <xsl:text>us.&#10;</xsl:text>
+        <xsl:text>                   </xsl:text>
+        <xsl:text>Measured latency when stopping was </xsl:text>
+        <xsl:value-of select="breaktrace/@measured_latency"/>
+        <xsl:text>us.&#10;&#10;</xsl:text>
+      </xsl:if>
   </xsl:template>
 
 </xsl:stylesheet>
diff --git a/rteval/rtevalclient.py b/rteval/rtevalclient.py
index 190958b..7ccf323 100644
--- a/rteval/rtevalclient.py
+++ b/rteval/rtevalclient.py
@@ -2,7 +2,7 @@
 #   rtevalclient.py
 #   XML-RPC client for sending data to a central rteval result server
 #
-#   Copyright 2009,2010      David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -14,9 +14,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/rteval/sysinfo/__init__.py b/rteval/sysinfo/__init__.py
new file mode 100644
index 0000000..0de985b
--- /dev/null
+++ b/rteval/sysinfo/__init__.py
@@ -0,0 +1,105 @@
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import sys, libxml2
+from rteval.Log import Log
+from glob import glob
+from kernel import KernelInfo
+from services import SystemServices
+from cputopology import CPUtopology
+from memory import MemoryInfo
+from osinfo import OSInfo
+from network import NetworkInfo
+import dmi
+
+class SystemInfo(KernelInfo, SystemServices, dmi.DMIinfo, CPUtopology, MemoryInfo, OSInfo, NetworkInfo):
+    def __init__(self, config, logger=None):
+        self.__logger = logger
+        KernelInfo.__init__(self, logger=logger)
+        SystemServices.__init__(self, logger=logger)
+        dmi.DMIinfo.__init__(self, config, logger=logger)
+        CPUtopology.__init__(self)
+        OSInfo.__init__(self, logger=logger)
+
+        # Parse initial DMI decoding errors
+        dmi.ProcessWarnings()
+
+        # Parse CPU info
+        CPUtopology._parse(self)
+
+
+    def MakeReport(self):
+        report_n = libxml2.newNode("SystemInfo");
+        report_n.newProp("version", "1.0")
+
+        # Populate the report
+        report_n.addChild(OSInfo.MakeReport(self))
+        report_n.addChild(KernelInfo.MakeReport(self))
+        report_n.addChild(NetworkInfo.MakeReport(self))
+        report_n.addChild(SystemServices.MakeReport(self))
+        report_n.addChild(CPUtopology.MakeReport(self))
+        report_n.addChild(MemoryInfo.MakeReport(self))
+        report_n.addChild(dmi.DMIinfo.MakeReport(self))
+
+        return report_n
+
+
+if __name__ == "__main__":
+    from rtevalConfig import rtevalConfig
+    l = Log()
+    l.SetLogVerbosity(Log.INFO|Log.DEBUG)
+    cfg = rtevalConfig(logger=l)
+    cfg.Load("../rteval.conf")
+    cfg.installdir = "."
+    si = SystemInfo(cfg, logger=l)
+
+    print "\tRunning on %s" % si.get_base_os()
+    print "\tNUMA nodes: %d" % si.mem_get_numa_nodes()
+    print "\tMemory available: %03.2f %s\n" % si.mem_get_size()
+
+    print "\tServices: "
+    for (s, r) in si.services_get().items():
+        print "\t\t%s: %s" % (s, r)
+    (curr, avail) = si.kernel_get_clocksources()
+
+    print "\tCurrent clocksource: %s" % curr
+    print "\tAvailable clocksources: %s" % avail
+    print "\tModules:"
+    for m in si.kernel_get_modules():
+        print "\t\t%s: %s" % (m['modname'], m['modstate'])
+    print "\tKernel threads:"
+    for (p, i) in si.kernel_get_kthreads().items():
+        print "\t\t%-30.30s pid: %-5.5s policy: %-7.7s prio: %-3.3s" % (
+            i["name"]+":", p, i["policy"], i["priority"]
+            )
+
+    print "\n\tCPU topology info - cores: %i  online: %i  sockets: %i" % (
+        si.cpu_getCores(False), si.cpu_getCores(True), si.cpu_getSockets()
+        )
+
+    xml = si.MakeReport()
+    xml_d = libxml2.newDoc("1.0")
+    xml_d.setRootElement(xml)
+    xml_d.saveFormatFileEnc("-", "UTF-8", 1)
diff --git a/rteval/cputopology.py b/rteval/sysinfo/cputopology.py
similarity index 88%
rename from rteval/cputopology.py
rename to rteval/sysinfo/cputopology.py
index 14631eb..73ea325 100644
--- a/rteval/cputopology.py
+++ b/rteval/sysinfo/cputopology.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
-#!/usr/bin/python -tt
 #
-#   Copyright 2010   David Sommerseth <davids@redhat.com>
+#   Copyright 2010 - 2013   David Sommerseth <davids@redhat.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
@@ -13,9 +12,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
@@ -30,7 +29,7 @@
     "Retrieves an overview over the installed CPU cores and the system topology"
 
     def __init__(self, root="/"):
-        self.sysdir = root + "/sys/devices/system/cpu"
+        self.sysdir = os.path.join(root, 'sys', 'devices', 'system', 'cpu')
         self.__cputop_n = None
         self.__cpu_cores = 0
         self.__online_cores = 0
@@ -46,10 +45,10 @@
         fp.close()
         return ret
 
-    def parse(self):
+    def _parse(self):
         "Parses the cpu topology information from /sys/devices/system/cpu/cpu*"
 
-        self.__cputop_n = libxml2.newNode('cpu_topology')
+        self.__cputop_n = libxml2.newNode('CPUtopology')
 
         cpusockets = []
         for dirname in os.listdir(self.sysdir):
@@ -100,19 +99,23 @@
 
         return self.__cputop_n
 
-    def getXMLdata(self):
+
+    def MakeReport(self):
         return self.__cputop_n
 
-    def getCPUcores(self, only_online):
+
+    def cpu_getCores(self, only_online):
         return only_online and self.__online_cores or self.__cpu_cores
 
-    def getCPUsockets(self):
+
+    def cpu_getSockets(self):
         return self.__cpu_sockets
 
+
 def unit_test(rootdir):
     try:
         cputop = CPUtopology()
-        n = cputop.parse()
+        n = cputop._parse()
 
         print " ---- XML Result ---- "
         x = libxml2.newDoc('1.0')
@@ -120,9 +123,9 @@
         x.saveFormatFileEnc('-','UTF-8',1)
 
         print " ---- getCPUcores() / getCPUscokets() ---- "
-        print "CPU cores: %i (online: %i) - CPU sockets: %i" % (cputop.getCPUcores(False),
-                                                                cputop.getCPUcores(True),
-                                                                cputop.getCPUsockets())
+        print "CPU cores: %i (online: %i) - CPU sockets: %i" % (cputop.cpu_getCores(False),
+                                                                cputop.cpu_getCores(True),
+                                                                cputop.cpu_getSockets())
         return 0
     except Exception, e:
         # import traceback
diff --git a/rteval/sysinfo/dmi.py b/rteval/sysinfo/dmi.py
new file mode 100644
index 0000000..f8e2500
--- /dev/null
+++ b/rteval/sysinfo/dmi.py
@@ -0,0 +1,145 @@
+#
+#   dmi.py - class to wrap DMI Table information
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import sys, os
+import libxml2, lxml.etree
+from rteval.Log import Log
+from rteval import xmlout
+from rteval import rtevalConfig
+
+try:
+    import dmidecode
+    dmidecode_loaded = True
+except:
+    dmidecode_loaded = False
+    pass
+
+def ProcessWarnings():
+
+    if not hasattr(dmidecode, 'get_warnings'):
+        return
+
+    warnings = dmidecode.get_warnings()
+    if warnings == None:
+        return
+
+    for warnline in warnings.split('\n'):
+        # Ignore these warnings, as they are "valid" if not running as root
+        if warnline == '/dev/mem: Permission denied':
+            continue
+        if warnline == 'No SMBIOS nor DMI entry point found, sorry.':
+            continue
+
+        # All other warnings will be printed
+        if len(warnline) > 0:
+            print "** DMI WARNING ** %s" % warnline
+
+    dmidecode.clear_warnings()
+
+
+class DMIinfo(object):
+    '''class used to obtain DMI info via python-dmidecode'''
+
+    def __init__(self, config, logger):
+        self.__version = '0.5'
+
+        if not dmidecode_loaded:
+            logger.log(Log.DEBUG|Log.WARN, "No dmidecode module found, ignoring DMI tables")
+            self.__fake = True
+            return
+
+        self.__fake = False
+        self.__dmixml = dmidecode.dmidecodeXML()
+
+        self.__xsltparser = self.__load_xslt('rteval_dmi.xsl')
+
+
+    def __load_xslt(self, fname):
+        xsltfile = None
+        if os.path.exists(fname):
+            xsltfile = open(fname, "r")
+        elif rtevalConfig.default_config_search([fname], os.path.isfile):
+            xsltfile = open(rtevalConfig.default_config_search([fname], os.path.isfile), "r")
+
+        if xsltfile:
+            xsltdoc = lxml.etree.parse(xsltfile)
+            ret = lxml.etree.XSLT(xsltdoc)
+            xsltfile.close()
+            return ret
+
+        raise RuntimeError, 'Could not locate XSLT template for DMI data (%s)' % (self.sharedir + '/' + fname)
+
+
+    def MakeReport(self):
+        rep_n = libxml2.newNode("DMIinfo")
+        rep_n.newProp("version", self.__version)
+        if self.__fake:
+            rep_n.addContent("No DMI tables available")
+            rep_n.newProp("not_available", "1")
+        else:
+            self.__dmixml.SetResultType(dmidecode.DMIXML_DOC)
+            dmiqry = xmlout.convert_libxml2_to_lxml_doc(self.__dmixml.QuerySection('all'))
+            resdoc = self.__xsltparser(dmiqry)
+            dmi_n = xmlout.convert_lxml_to_libxml2_nodes(resdoc.getroot())
+            rep_n.addChild(dmi_n)
+        return rep_n
+
+
+
+def unit_test(rootdir):
+    from pprint import pprint
+
+    class unittest_ConfigDummy(object):
+        def __init__(self, rootdir):
+            self.config = {'installdir': '/usr/share/rteval'}
+            self.__update_vars()
+
+        def __update_vars(self):
+            for k in self.config.keys():
+                self.__dict__[k] = self.config[k]
+
+    try:
+        ProcessWarnings()
+        if os.getuid() != 0:
+            print "** ERROR **  Must be root to run this unit_test()"
+            return 1
+
+        log = Log()
+        log.SetLogVerbosity(Log.DEBUG|Log.INFO)
+        cfg = unittest_ConfigDummy(rootdir)
+        d = DMIinfo(cfg, log)
+        dx = d.MakeReport()
+        x = libxml2.newDoc("1.0")
+        x.setRootElement(dx)
+        x.saveFormatFileEnc("-", "UTF-8", 1)
+        return 0
+    except Exception, e:
+        print "** EXCEPTION: %s" % str(e)
+        return 1
+
+if __name__ == '__main__':
+    sys.exit(unit_test('.'))
diff --git a/rteval/sysinfo/kernel.py b/rteval/sysinfo/kernel.py
new file mode 100644
index 0000000..209afd7
--- /dev/null
+++ b/rteval/sysinfo/kernel.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.com>
+#   Copyright 2012 - 2013   Raphaël Beamonte <raphael.beamonte@gmail.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import sys, subprocess, os, libxml2
+from rteval.sysinfo.tools import getcmdpath
+from rteval.Log import Log
+
+
+class KernelInfo(object):
+    def __init__(self, logger = None):
+        self.__logger = logger
+
+
+    def __log(self, logtype, msg):
+        if self.__logger:
+            self.__logger.log(logtype, msg)
+
+
+    def kernel_get_kthreads(self):
+        policies = {'FF':'fifo', 'RR':'rrobin', 'TS':'other', '?':'unknown' }
+        ret_kthreads = {}
+        self.__log(Log.DEBUG, "getting kthread status")
+        cmd = '%s -eocommand,pid,policy,rtprio,comm' % getcmdpath('ps')
+        self.__log(Log.DEBUG, "cmd: %s" % cmd)
+        c = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+        for p in c.stdout:
+            v = p.strip().split()
+            kcmd = v.pop(0)
+            try:
+                if int(v[0]) > 0 and kcmd.startswith('[') and kcmd.endswith(']'):
+                    ret_kthreads[v[0]] = {'policy' : policies[v[1]],
+                                          'priority' : v[2], 'name' : v[3] }
+            except ValueError:
+                pass    # Ignore lines which don't have a number in the first row
+        return ret_kthreads
+
+
+    def kernel_get_modules(self):
+        modlist = []
+        try:
+            fp = open('/proc/modules', 'r')
+            line = fp.readline()
+            while line:
+                mod = line.split()
+                modlist.append({"modname": mod[0],
+                                "modsize": mod[1],
+                                "numusers": mod[2],
+                                "usedby": mod[3],
+                                "modstate": mod[4]})
+                line = fp.readline()
+            fp.close()
+        except Exception, err:
+            raise err
+        return modlist
+
+
+    def kernel_get_clocksources(self):
+        '''get the available and curent clocksources for this kernel'''
+        path = '/sys/devices/system/clocksource/clocksource0'
+        if not os.path.exists(path):
+            raise RuntimeError, "Can't find clocksource path in /sys"
+        f = open (os.path.join (path, "current_clocksource"))
+        current_clocksource = f.readline().strip()
+        f = open (os.path.join (path, "available_clocksource"))
+        available_clocksource = f.readline().strip()
+        f.close()
+        return (current_clocksource, available_clocksource)
+
+
+    def MakeReport(self):
+        rep_n = libxml2.newNode("Kernel")
+
+        clksrc = self.kernel_get_clocksources()
+        clock_n = libxml2.newNode("ClockSource")
+        rep_n.addChild(clock_n)
+        for avail in clksrc[1].split():
+            avail_n = libxml2.newNode("source")
+            avail_n.addContent(avail)
+            if avail == clksrc[0]:
+                avail_n.newProp("current", "1")
+            clock_n.addChild(avail_n)
+
+        mods_n = libxml2.newNode("Modules")
+        rep_n.addChild(mods_n)
+
+        for mod in self.kernel_get_modules():
+            mod_n = libxml2.newNode("Module")
+            mods_n.addChild(mod_n)
+
+            mod_n.newProp("name", mod["modname"])
+
+            mod_n.newProp("size", str(mod["modsize"]))
+            mod_n.newProp("state", mod["modstate"])
+            mod_n.newProp("numusers", str(mod["numusers"]))
+
+            if mod["usedby"] != "-":
+                usedby_n = libxml2.newNode("usedby")
+                mod_n.addChild(usedby_n)
+                for ub in mod["usedby"].split(","):
+                    if len(ub):
+                        ub_n = libxml2.newNode("module")
+                        ub_n.addContent(ub)
+                        usedby_n.addChild(ub_n)
+
+
+        kthreads_n = libxml2.newNode("kthreads")
+        rep_n.addChild(kthreads_n)
+
+        kthreads = self.kernel_get_kthreads()
+        keys = kthreads.keys()
+        if len(keys):
+            keys.sort()
+            for pid in keys:
+                kthri_n = libxml2.newNode("thread")
+                kthreads_n.addChild(kthri_n)
+                kthri_n.addContent(kthreads[pid]["name"])
+                kthri_n.newProp("policy", kthreads[pid]["policy"])
+                kthri_n.newProp("priority", kthreads[pid]["priority"])
+
+        return rep_n
+
+
+
+def unit_test(rootdir):
+    try:
+        from pprint import pprint
+        log = Log()
+        log.SetLogVerbosity(Log.INFO|Log.DEBUG)
+
+        ki = KernelInfo(logger=log)
+        pprint(ki.kernel_get_kthreads())
+        pprint(ki.kernel_get_modules())
+        pprint(ki.kernel_get_clocksources())
+
+        ki_xml = ki.MakeReport()
+        xml_d = libxml2.newDoc("1.0")
+        xml_d.setRootElement(ki_xml)
+        xml_d.saveFormatFileEnc("-", "UTF-8", 1)
+
+    except Exception, e:
+        import traceback
+        traceback.print_exc(file=sys.stdout)
+        print "** EXCEPTION %s", str(e)
+        return 1
+
+if __name__ == '__main__':
+    unit_test(None)
+
diff --git a/rteval/sysinfo/memory.py b/rteval/sysinfo/memory.py
new file mode 100644
index 0000000..2c31d65
--- /dev/null
+++ b/rteval/sysinfo/memory.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2012 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import libxml2
+from glob import glob
+
+class MemoryInfo(object):
+    numa_nodes = None
+
+    def __init__(self):
+        pass
+
+
+    def mem_get_numa_nodes(self):
+        if self.numa_nodes is None:
+            self.numa_nodes = len(glob('/sys/devices/system/node/node*'))
+        return self.numa_nodes
+
+
+    def mem_get_size(self):
+        '''find out how much memory is installed'''
+        f = open('/proc/meminfo')
+        rawsize = 0
+        for l in f:
+            if l.startswith('MemTotal:'):
+                parts = l.split()
+                if parts[2].lower() != 'kb':
+                    raise RuntimeError, "Units changed from kB! (%s)" % parts[2]
+                rawsize = int(parts[1])
+                f.close()
+                break
+        if rawsize == 0:
+            raise RuntimeError, "can't find memtotal in /proc/meminfo!"
+
+        # Get a more readable result
+        # Note that this depends on  /proc/meminfo starting in Kb
+        units = ('KB', 'MB','GB','TB')
+        size = rawsize
+        for unit in units:
+            if size < (1024*1024):
+                break
+            size = float(size) / 1024
+        return (size, unit)
+
+
+    def MakeReport(self):
+        rep_n = libxml2.newNode("Memory")
+
+        numa_n = libxml2.newNode("numa_nodes")
+        numa_n.addContent(str(self.mem_get_numa_nodes()))
+        rep_n.addChild(numa_n)
+
+        memsize = self.mem_get_size()
+        mem_n = libxml2.newNode("memory_size")
+        mem_n.addContent("%.3f" % memsize[0])
+        mem_n.newProp("unit", memsize[1])
+        rep_n.addChild(mem_n)
+
+        return rep_n
+
+
+
+def unit_test(rootdir):
+    import sys
+    try:
+        mi = MemoryInfo()
+        print "Numa nodes: %i" % mi.mem_get_numa_nodes()
+        print "Memory: %i %s" % mi.mem_get_size()
+    except Exception, e:
+        import traceback
+        traceback.print_exc(file=sys.stdout)
+        print "** EXCEPTION %s", str(e)
+        return 1
+
+if __name__ == '__main__':
+    unit_test(None)
diff --git a/rteval/sysinfo/network.py b/rteval/sysinfo/network.py
new file mode 100644
index 0000000..fa29db2
--- /dev/null
+++ b/rteval/sysinfo/network.py
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+#
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import ethtool, libxml2
+
+class NetworkInfo(object):
+    def __init__(self):
+        pass
+
+    def net_GetDefaultGW(self):
+        # Get the interface name for the IPv4 default gw
+        route = open('/proc/net/route')
+        defgw4 = None
+        if route:
+            rl = route.readline()
+            while rl != '' :
+                rl = route.readline()
+                splt = rl.split("\t")
+                # Only catch default route
+                if len(splt) > 2 and splt[2] != '00000000' and splt[1] == '00000000':
+                    defgw4 = splt[0]
+                    break
+            route.close()
+        return (defgw4, None) # IPv6 gw not yet implemented
+
+    def MakeReport(self):
+        ncfg_n = libxml2.newNode("NetworkConfig")
+        (defgw4, defgw6) = self.net_GetDefaultGW()
+
+        # Make an interface tag for each device found
+        if hasattr(ethtool, 'get_interfaces_info'):
+            # Using the newer python-ethtool API (version >= 0.4)
+            for dev in ethtool.get_interfaces_info(ethtool.get_devices()):
+                if cmp(dev.device,'lo') == 0:
+                    continue
+
+                intf_n = libxml2.newNode('interface')
+                intf_n.newProp('device', dev.device)
+                intf_n.newProp('hwaddr', dev.mac_address)
+                ncfg_n.addChild(intf_n)
+
+                # Protcol configurations
+                if dev.ipv4_address:
+                    ipv4_n = libxml2.newNode('IPv4')
+                    ipv4_n.newProp('ipaddr', dev.ipv4_address)
+                    ipv4_n.newProp('netmask', str(dev.ipv4_netmask))
+                    ipv4_n.newProp('broadcast', dev.ipv4_broadcast)
+                    ipv4_n.newProp('defaultgw', (defgw4 == dev.device) and '1' or '0')
+                    intf_n.addChild(ipv4_n)
+
+                for ip6 in dev.get_ipv6_addresses():
+                    ipv6_n = libxml2.newNode('IPv6')
+                    ipv6_n.newProp('ipaddr', ip6.address)
+                    ipv6_n.newProp('netmask', str(ip6.netmask))
+                    ipv6_n.newProp('scope', ip6.scope)
+                    intf_n.addChild(ipv6_n)
+
+        else: # Fall back to older python-ethtool API (version < 0.4)
+            ifdevs = ethtool.get_active_devices()
+            ifdevs.remove('lo')
+            ifdevs.sort()
+
+            for dev in ifdevs:
+                intf_n = libxml2.newNode('interface')
+                intf_n.newProp('device', dev.device)
+                intf_n.newProp('hwaddr', dev.mac_address)
+                ncfg_n.addChild(intf_n)
+
+                ipv4_n = libxml2.newNode('IPv4')
+                ipv4_n.newProp('ipaddr', ethtool.get_ipaddr(dev))
+                ipv4_n.newProp('netmask', str(ethtool.get_netmask(dev)))
+                ipv4_n.newProp('defaultgw', (defgw4 == dev) and '1' or '0')
+                intf_n.addChild(ipv4_n)
+
+        return ncfg_n
+
+
+def unit_test(rootdir):
+    import sys
+    try:
+        net = NetworkInfo()
+        doc = libxml2.newDoc('1.0')
+        cfg = net.MakeReport()
+        doc.setRootElement(cfg)
+        doc.saveFormatFileEnc('-', 'UTF-8', 1)
+
+    except Exception, e:
+        import traceback
+        traceback.print_exc(file=sys.stdout)
+        print "** EXCEPTION %s", str(e)
+        return 1
+
+if __name__ == '__main__':
+    unit_test(None)
+
diff --git a/rteval/sysinfo/osinfo.py b/rteval/sysinfo/osinfo.py
new file mode 100644
index 0000000..e262c06
--- /dev/null
+++ b/rteval/sysinfo/osinfo.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2012 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import os, shutil, subprocess, libxml2
+from glob import glob
+from rteval.Log import Log
+
+class OSInfo(object):
+    def __init__(self, logger):
+        self.__logger = logger
+
+
+    def get_base_os(self):
+        '''record what userspace we're running on'''
+        distro = "unknown"
+        for f in ('redhat-release', 'fedora-release'):
+            p = os.path.join('/etc', f)
+            if os.path.exists(p):
+                f = open(p, 'r')
+                distro = f.readline().strip()
+                f.close()
+                break
+        return distro
+
+
+    def copy_dmesg(self, repdir):
+        dpath = "/var/log/dmesg"
+        if os.path.exists(dpath):
+            shutil.copyfile(dpath, os.path.join(repdir, "dmesg"))
+            return
+        if os.path.exists('/usr/bin/dmesg'):
+            subprocess.call('/usr/bin/dmesg > %s' % os.path.join(repdir, "dmesg"), shell=True)
+            return
+        print "dmesg file not found at %s and no dmesg exe found!" % dpath
+
+
+
+    def run_sysreport(self, repdir):
+        if os.path.exists('/usr/sbin/sosreport'):
+            exe = '/usr/sbin/sosreport'
+        elif os.path.exists('/usr/sbin/sysreport'):
+            exe = '/usr/sbin/sysreport'
+        else:
+            raise RuntimeError, "Can't find sosreport/sysreport"
+
+        self.__logger.log(Log.DEBUG, "report tool: %s" % exe)
+        options =  ['-k', 'rpm.rpmva=off',
+                    '--name=rteval',
+                    '--batch']
+
+        self.__logger.log(Log.INFO, "Generating SOS report")
+        self.__logger.log(Log.INFO, "using command %s" % " ".join([exe]+options))
+        subprocess.call([exe] + options)
+        for s in glob('/tmp/s?sreport-rteval-*'):
+            self.__logger.log(Log.DEBUG, "moving %s to %s" % (s, repdir))
+            shutil.move(s, repdir)
+
+
+    def MakeReport(self):
+        rep_n = libxml2.newNode("uname")
+
+        baseos_n = libxml2.newNode("baseos")
+        baseos_n.addContent(self.get_base_os())
+        rep_n.addChild(baseos_n)
+
+        (sys, node, release, ver, machine) = os.uname()
+        isrt = 1
+        if ver.find(' RT ') == -1:
+            isrt = 0
+
+        node_n = libxml2.newNode("node")
+        node_n.addContent(node)
+        rep_n.addChild(node_n)
+
+        arch_n = libxml2.newNode("arch")
+        arch_n.addContent(machine)
+        rep_n.addChild(arch_n)
+
+        kernel_n = libxml2.newNode("kernel")
+        kernel_n.newProp("is_RT", str(isrt))
+        kernel_n.addContent(release)
+        rep_n.addChild(kernel_n)
+
+        return rep_n
+
+
+
+def unit_test(rootdir):
+    import sys
+
+    try:
+        log = Log()
+        log.SetLogVerbosity(Log.DEBUG|Log.INFO)
+        osi = OSInfo(logger=log)
+        print "Base OS: %s" % osi.get_base_os()
+
+        print "Testing OSInfo::copy_dmesg('/tmp'): ",
+        osi.copy_dmesg('/tmp')
+        if os.path.isfile("/tmp/dmesg"):
+            md5orig = subprocess.check_output(("md5sum","/var/log/dmesg"))
+            md5copy = subprocess.check_output(("md5sum","/tmp/dmesg"))
+            if md5orig.split(" ")[0] == md5copy.split(" ")[0]:
+                print "PASS"
+            else:
+                print "FAIL (md5sum)"
+            os.unlink("/tmp/dmesg")
+        else:
+            print "FAIL (copy failed)"
+
+        print "Running sysreport/sosreport with output to current dir"
+        osi.run_sysreport(".")
+
+        osinfo_xml = osi.MakeReport()
+        xml_d = libxml2.newDoc("1.0")
+        xml_d.setRootElement(osinfo_xml)
+        xml_d.saveFormatFileEnc("-", "UTF-8", 1)
+
+    except Exception, e:
+        import traceback
+        traceback.print_exc(file=sys.stdout)
+        print "** EXCEPTION %s", str(e)
+        return 1
+
+if __name__ == '__main__':
+    unit_test(None)
diff --git a/rteval/sysinfo/services.py b/rteval/sysinfo/services.py
new file mode 100644
index 0000000..e7c2fe0
--- /dev/null
+++ b/rteval/sysinfo/services.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+#
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.com>
+#   Copyright 2012 - 2013   Raphaël Beamonte <raphael.beamonte@gmail.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import sys, subprocess, os, glob, fnmatch, libxml2
+from rteval.sysinfo.tools import getcmdpath
+from rteval.Log import Log
+
+
+class SystemServices(object):
+    def __init__(self, logger=None):
+        self.__logger = logger
+        self.__init = "unknown"
+
+    def __log(self, logtype, msg):
+        if self.__logger:
+            self.__logger.log(logtype, msg)
+
+
+    def __get_services_sysvinit(self):
+        reject = ('functions', 'halt', 'killall', 'single', 'linuxconf', 'kudzu',
+                  'skeleton', 'README', '*.dpkg-dist', '*.dpkg-old', 'rc', 'rcS',
+                  'single', 'reboot', 'bootclean.sh')
+        for sdir in ('/etc/init.d', '/etc/rc.d/init.d'):
+            if os.path.isdir(sdir):
+                servicesdir = sdir
+                break
+        if not servicesdir:
+            raise RuntimeError, "No services dir (init.d) found on your system"
+        self.__log(Log.DEBUG, "Services located in %s, going through each service file to check status" % servicesdir)
+        ret_services = {}
+        for service in glob.glob(os.path.join(servicesdir, '*')):
+            servicename = os.path.basename(service)
+            if not [1 for p in reject if fnmatch.fnmatch(servicename, p)] and os.access(service, os.X_OK):
+                cmd = '%s -qs "\(^\|\W\)status)" %s' % (getcmdpath('grep'), service)
+                c = subprocess.Popen(cmd, shell=True)
+                c.wait()
+                if c.returncode == 0:
+                    cmd = ['env', '-i', 'LANG="%s"' % os.environ['LANG'], 'PATH="%s"' % os.environ['PATH'], 'TERM="%s"' % os.environ['TERM'], service, 'status']
+                    c = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+                    c.wait()
+                    if c.returncode == 0 and (c.stdout.read() or c.stderr.read()):
+                        ret_services[servicename] = 'running'
+                    else:
+                        ret_services[servicename] = 'not running'
+                else:
+                    ret_services[servicename] = 'unknown'
+        return ret_services
+
+
+    def __get_services_systemd(self):
+        ret_services = {}
+        cmd = '%s list-unit-files -t service --no-legend' % getcmdpath('systemctl')
+        self.__log(Log.DEBUG, "cmd: %s" % cmd)
+        c = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        for p in c.stdout:
+            # p are lines like "servicename.service status"
+            v = p.strip().split()
+            ret_services[v[0].split('.')[0]] = v[1]
+        return ret_services
+
+
+    def services_get(self):
+        cmd = [getcmdpath('ps'), '-ocomm=',  '1']
+        c = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+        self.__init = c.stdout.read().strip()
+        if self.__init == 'systemd':
+            self.__log(Log.DEBUG, "Using systemd to get services status")
+            return self.__get_services_systemd()
+        elif self.__init == 'init':
+            self.__init = 'sysvinit'
+            self.__log(Log.DEBUG, "Using sysvinit to get services status")
+            return self.__get_services_sysvinit()
+        else:
+            raise RuntimeError, "Unknown init system (%s)" % self.__init
+        return {}
+
+
+    def MakeReport(self):
+        srvs = self.services_get()
+
+        rep_n = libxml2.newNode("Services")
+        rep_n.newProp("init", self.__init)
+
+        for s in srvs:
+            srv_n = libxml2.newNode("Service")
+            srv_n.newProp("state", srvs[s])
+            srv_n.addContent(s)
+            rep_n.addChild(srv_n)
+
+        return rep_n
+
+def unit_test(rootdir):
+    from pprint import pprint
+
+    try:
+        syssrv = SystemServices()
+        pprint(syssrv.services_get())
+
+        srv_xml = syssrv.MakeReport()
+        xml_d = libxml2.newDoc("1.0")
+        xml_d.setRootElement(srv_xml)
+        xml_d.saveFormatFileEnc("-", "UTF-8", 1)
+
+        return 0
+    except Exception, e:
+        print "** EXCEPTION: %s" % str(e)
+        return 1
+
+
+if __name__ == '__main__':
+    sys.exit(unit_test(None))
+
diff --git a/rteval/sysinfo/systopology.py b/rteval/sysinfo/systopology.py
new file mode 100644
index 0000000..a6e5c1a
--- /dev/null
+++ b/rteval/sysinfo/systopology.py
@@ -0,0 +1,237 @@
+# -*- coding: utf-8 -*-
+#
+#   Copyright 2016 - Clark Williams <williams@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import os, sys
+import os.path
+import glob
+
+def _sysread(path, obj):
+    fp = open(os.path.join(path,obj), "r")
+    return fp.readline().strip()
+
+#
+# class to provide access to a list of cpus
+#
+
+class CpuList(object):
+    "Object that represents a group of system cpus"
+
+    cpupath = '/sys/devices/system/cpu'
+
+    def __init__(self, cpulist):
+        if type(cpulist) is list:
+            self.cpulist = cpulist
+        elif type(cpulist) is str:
+            self.cpulist = self.__expand_cpulist(cpulist)
+        self.cpulist.sort()
+
+    def __str__(self):
+        return self.__collapse_cpulist(self.cpulist)
+
+    def __contains__(self, cpu):
+        return cpu in self.cpulist
+
+    def __len__(self):
+        return len(self.cpulist)
+
+
+    # return the index of the last element of a sequence
+    # that steps by one
+    def __longest_sequence(self, cpulist):
+        lim = len(cpulist)
+        for idx,val in enumerate(cpulist):
+            if idx+1 == lim:
+                break
+            if int(cpulist[idx+1]) != (int(cpulist[idx])+1):
+                return idx
+        return lim - 1
+
+
+    #
+    # collapse a list of cpu numbers into a string range
+    # of cpus (e.g. 0-5, 7, 9)
+    #
+    def __collapse_cpulist(self, cpulist):
+        if len(cpulist) == 0:
+            return ""
+        idx = self.__longest_sequence(cpulist)
+        if idx == 0:
+            seq = str(cpulist[0])
+        else:
+            if idx == 1:
+                seq = "%d,%d" % (cpulist[0], cpulist[idx])
+            else:
+                seq = "%d-%d" % (cpulist[0], cpulist[idx])
+
+        rest = self.__collapse_cpulist(cpulist[idx+1:])
+        if rest == "":
+            return seq
+        return ",".join((seq, rest))
+
+    # expand a string range into a list
+    # don't error check against online cpus
+    def __expand_cpulist(self, cpulist):
+        '''expand a range string into an array of cpu numbers'''
+        result = []
+        for part in cpulist.split(','):
+            if '-' in part:
+                a, b = part.split('-')
+                a, b = int(a), int(b)
+                result.extend(range(a, b + 1))
+            else:
+                a = int(part)
+                result.append(a)
+        return [ int(i) for i in list(set(result)) ]
+
+    # returns the list of cpus tracked
+    def getcpulist(self):
+        return self.cpulist
+
+    # check whether cpu n is online
+    def isonline(self, n):
+        if n not in self.cpulist:
+            raise RuntimeError, "invalid cpu number %d" % n
+        if n == 0:
+            return True
+        path = os.path.join(CpuList.cpupath,'cpu%d' % n)
+        if os.path.exists(path):
+            return _sysread(path, "online") == 1
+        return False
+
+#
+# class to abstract access to NUMA nodes in /sys filesystem
+#
+
+class NumaNode(object):
+    "class representing a system NUMA node"
+
+    # constructor argument is the full path to the /sys node file
+    # e.g. /sys/devices/system/node/node0
+    def __init__(self, path):
+        self.path = path
+        self.nodeid = int(os.path.basename(path)[4:].strip())
+        self.cpus = CpuList(_sysread(self.path, "cpulist"))
+        self.getmeminfo()
+
+    # function for the 'in' operator
+    def __contains__(self, cpu):
+        return cpu in self.cpus
+
+    # allow the 'len' builtin
+    def __len__(self):
+        return len(self.cpus)
+
+    # string representation of the cpus for this node
+    def __str__(self):
+        return self.getcpustr()
+
+    # read info about memory attached to this node
+    def getmeminfo(self):
+        self.meminfo = {}
+        for l in open(os.path.join(self.path, "meminfo"), "r"):
+            elements = l.split()
+            key=elements[2][0:-1]
+            val=int(elements[3])
+            if len(elements) == 5 and elements[4] == "kB":
+                val *= 1024
+            self.meminfo[key] = val
+
+    # return list of cpus for this node as a string
+    def getcpustr(self):
+        return str(self.cpus)
+
+    # return list of cpus for this node
+    def getcpulist(self):
+        return self.cpus.getcpulist()
+
+#
+# Class to abstract the system topology of numa nodes and cpus
+#
+class SysTopology(object):
+    "Object that represents the system's NUMA-node/cpu topology"
+
+    cpupath = '/sys/devices/system/cpu'
+    nodepath = '/sys/devices/system/node'
+
+    def __init__(self):
+        self.nodes = {}
+        self.getinfo()
+
+    def __len__(self):
+        return len(self.nodes.keys())
+
+    # inplement the 'in' function
+    def __contains__(self, node):
+        for n in self.nodes:
+            if self.nodes[n].nodeid == node:
+                return True
+        return False
+
+    # allow indexing for the nodes
+    def __getitem__(self, key):
+        return self.nodes[key]
+
+    # allow iteration over the cpus for the node
+    def __iter__(self):
+        self.current = 0
+        return self
+
+    # iterator function
+    def next(self):
+        if self.current >= len(self.nodes):
+            raise StopIteration
+        n = self.nodes[self.current]
+        self.current += 1
+        return n
+
+    def getinfo(self):
+        nodes = glob.glob(os.path.join(SysTopology.nodepath, 'node[0-9]*'))
+        if not nodes:
+            raise RuntimeError, "No valid nodes found in %s!" % SysTopology.nodepath
+        nodes.sort()
+        for n in nodes:
+            node = int(os.path.basename(n)[4:])
+            self.nodes[node] = NumaNode(n)
+
+    def getnodes(self):
+        return self.nodes.keys()
+
+    def getcpus(self, node):
+        return self.nodes[node]
+
+
+
+if __name__ == "__main__":
+
+    def unit_test():
+        s = SysTopology()
+        print "number of nodes: %d" % len(s)
+        for n in s:
+            print "node[%d]: %s" % (n.nodeid, n)
+        print "system has numa node 0: %s" % (0 in s)
+        print "system has numa node 2: %s" % (2 in s)
+        print "system has numa node 24: %s" % (24 in s)
+
+    unit_test()
diff --git a/rteval/sysinfo/tools.py b/rteval/sysinfo/tools.py
new file mode 100644
index 0000000..a61aa48
--- /dev/null
+++ b/rteval/sysinfo/tools.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+#
+#   Copyright 2012 - 2013   Raphaël Beamonte <raphael.beamonte@gmail.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import os
+import os.path
+
+pathSave={}
+def getcmdpath(which):
+    """
+    getcmdpath is a method which allows finding an executable in the PATH
+    directories to call it from full path
+    """
+    if not pathSave.has_key(which):
+        for path in os.environ['PATH'].split(':'):
+            cmdfile = os.path.join(path, which)
+            if os.path.isfile(cmdfile) and os.access(cmdfile, os.X_OK):
+                pathSave[which] = cmdfile
+                break
+        if not pathSave[which]:
+            raise RuntimeError, "Command '%s' is unknown on this system" % which
+    return pathSave[which]
+
diff --git a/rteval/systopology.py b/rteval/systopology.py
new file mode 100644
index 0000000..7c0985e
--- /dev/null
+++ b/rteval/systopology.py
@@ -0,0 +1,246 @@
+# -*- coding: utf-8 -*-
+#
+#   Copyright 2016 - Clark Williams <williams@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+import os, sys
+import os.path
+import glob
+
+def _sysread(path, obj):
+    fp = open(os.path.join(path,obj), "r")
+    return fp.readline().strip()
+
+#
+# class to provide access to a list of cpus
+#
+
+class CpuList(object):
+    "Object that represents a group of system cpus"
+
+    cpupath = '/sys/devices/system/cpu'
+
+    def __init__(self, cpulist):
+        if type(cpulist) is list:
+            self.cpulist = cpulist
+        elif type(cpulist) is str:
+            self.cpulist = self.__expand_cpulist(cpulist)
+        self.cpulist.sort()
+
+    def __str__(self):
+        return self.__collapse_cpulist(self.cpulist)
+
+    def __contains__(self, cpu):
+        return cpu in self.cpulist
+
+    def __len__(self):
+        return len(self.cpulist)
+
+
+    # return the index of the last element of a sequence
+    # that steps by one
+    def __longest_sequence(self, cpulist):
+        lim = len(cpulist)
+        for idx,val in enumerate(cpulist):
+            if idx+1 == lim:
+                break
+            if int(cpulist[idx+1]) != (int(cpulist[idx])+1):
+                return idx
+        return lim - 1
+
+
+    #
+    # collapse a list of cpu numbers into a string range
+    # of cpus (e.g. 0-5, 7, 9)
+    #
+    def __collapse_cpulist(self, cpulist):
+        if len(cpulist) == 0:
+            return ""
+        idx = self.__longest_sequence(cpulist)
+        if idx == 0:
+            seq = str(cpulist[0])
+        else:
+            if idx == 1:
+                seq = "%d,%d" % (cpulist[0], cpulist[idx])
+            else:
+                seq = "%d-%d" % (cpulist[0], cpulist[idx])
+
+        rest = self.__collapse_cpulist(cpulist[idx+1:])
+        if rest == "":
+            return seq
+        return ",".join((seq, rest))
+
+    # expand a string range into a list
+    # don't error check against online cpus
+    def __expand_cpulist(self, cpulist):
+        '''expand a range string into an array of cpu numbers'''
+        result = []
+        for part in cpulist.split(','):
+            if '-' in part:
+                a, b = part.split('-')
+                a, b = int(a), int(b)
+                result.extend(range(a, b + 1))
+            else:
+                a = int(part)
+                result.append(a)
+        return [ int(i) for i in list(set(result)) ]
+
+    # returns the list of cpus tracked
+    def getcpulist(self):
+        return self.cpulist
+
+    # check whether cpu n is online
+    def isonline(self, n):
+        if n not in self.cpulist:
+            raise RuntimeError, "invalid cpu number %d" % n
+        if n == 0:
+            return True
+        path = os.path.join(CpuList.cpupath,'cpu%d' % n)
+        if os.path.exists(path):
+            return _sysread(path, "online") == 1
+        return False
+
+#
+# class to abstract access to NUMA nodes in /sys filesystem
+#
+
+class NumaNode(object):
+    "class representing a system NUMA node"
+
+    # constructor argument is the full path to the /sys node file
+    # e.g. /sys/devices/system/node/node0
+    def __init__(self, path):
+        self.path = path
+        self.nodeid = int(os.path.basename(path)[4:].strip())
+        self.cpus = CpuList(_sysread(self.path, "cpulist"))
+        self.getmeminfo()
+
+    # function for the 'in' operator
+    def __contains__(self, cpu):
+        return cpu in self.cpus
+
+    # allow the 'len' builtin
+    def __len__(self):
+        return len(self.cpus)
+
+    # string representation of the cpus for this node
+    def __str__(self):
+        return self.getcpustr()
+
+    def __int__(self):
+        return self.nodeid
+
+    # read info about memory attached to this node
+    def getmeminfo(self):
+        self.meminfo = {}
+        for l in open(os.path.join(self.path, "meminfo"), "r"):
+            elements = l.split()
+            key=elements[2][0:-1]
+            val=int(elements[3])
+            if len(elements) == 5 and elements[4] == "kB":
+                val *= 1024
+            self.meminfo[key] = val
+
+    # return list of cpus for this node as a string
+    def getcpustr(self):
+        return str(self.cpus)
+
+    # return list of cpus for this node
+    def getcpulist(self):
+        return self.cpus.getcpulist()
+
+#
+# Class to abstract the system topology of numa nodes and cpus
+#
+class SysTopology(object):
+    "Object that represents the system's NUMA-node/cpu topology"
+
+    cpupath = '/sys/devices/system/cpu'
+    nodepath = '/sys/devices/system/node'
+
+    def __init__(self):
+        self.nodes = {}
+        self.getinfo()
+
+    def __len__(self):
+        return len(self.nodes.keys())
+
+    def __str__(self):
+        s = "%d node system" % len(self.nodes.keys())
+        s += " (%d cores per node)" % (len(self.nodes[self.nodes.keys()[0]]))
+        return s
+
+    # inplement the 'in' function
+    def __contains__(self, node):
+        for n in self.nodes:
+            if self.nodes[n].nodeid == node:
+                return True
+        return False
+
+    # allow indexing for the nodes
+    def __getitem__(self, key):
+        return self.nodes[key]
+
+    # allow iteration over the cpus for the node
+    def __iter__(self):
+        self.current = 0
+        return self
+
+    # iterator function
+    def next(self):
+        if self.current >= len(self.nodes):
+            raise StopIteration
+        n = self.nodes[self.current]
+        self.current += 1
+        return n
+
+    def getinfo(self):
+        nodes = glob.glob(os.path.join(SysTopology.nodepath, 'node[0-9]*'))
+        if not nodes:
+            raise RuntimeError, "No valid nodes found in %s!" % SysTopology.nodepath
+        nodes.sort()
+        for n in nodes:
+            node = int(os.path.basename(n)[4:])
+            self.nodes[node] = NumaNode(n)
+
+    def getnodes(self):
+        return self.nodes.keys()
+
+    def getcpus(self, node):
+        return self.nodes[node]
+
+
+
+if __name__ == "__main__":
+
+    def unit_test():
+        s = SysTopology()
+        print s
+        print "number of nodes: %d" % len(s)
+        for n in s:
+            print "node[%d]: %s" % (n.nodeid, n)
+        print "system has numa node 0: %s" % (0 in s)
+        print "system has numa node 2: %s" % (2 in s)
+        print "system has numa node 24: %s" % (24 in s)
+
+    unit_test()
diff --git a/rteval/util.py b/rteval/util.py
deleted file mode 100644
index 79834a8..0000000
--- a/rteval/util.py
+++ /dev/null
@@ -1,96 +0,0 @@
-#!/usr/bin/python -tt
-#
-# platform utility functions used by various parts of rteval
-#
-
-import sys
-import os
-import os.path
-import subprocess
-
-def get_base_os():
-    '''record what userspace we're running on'''
-    distro = "unknown"
-    for f in ('redhat-release', 'fedora-release'):
-        p = os.path.join('/etc', f)
-        if os.path.exists(p):
-            f = open(p, 'r')
-            distro = f.readline().strip()
-            f.close()
-            break
-    return distro
-
-def get_num_nodes():
-    from glob import glob
-    nodes = len(glob('/sys/devices/system/node/node*'))
-    return nodes
-        
-def get_memory_size():
-    '''find out how much memory is installed'''
-    f = open('/proc/meminfo')
-    rawsize = 0
-    for l in f:
-        if l.startswith('MemTotal:'):
-            parts = l.split()
-            if parts[2].lower() != 'kb':
-                raise RuntimeError, "Units changed from kB! (%s)" % parts[2]
-            rawsize = int(parts[1])
-            f.close()
-            break
-    if rawsize == 0:
-        raise RuntimeError, "can't find memtotal in /proc/meminfo!"
-
-    # Get a more readable result
-    # Note that this depends on  /proc/meminfo starting in Kb
-    units = ('KB', 'MB','GB','TB')
-    size = rawsize
-    for unit in units:
-        if size < 1024:
-            break
-        size = float(size) / 1024
-    return (size, unit)
-
-
-def get_clocksources():
-    '''get the available and curent clocksources for this kernel'''
-    path = '/sys/devices/system/clocksource/clocksource0'
-    if not os.path.exists(path):
-        raise RuntimeError, "Can't find clocksource path in /sys"
-    f = open (os.path.join (path, "current_clocksource"))
-    current_clocksource = f.readline().strip()
-    f = open (os.path.join (path, "available_clocksource"))
-    available_clocksource = f.readline().strip()
-    f.close()
-    return (current_clocksource, available_clocksource)
-
-
-def get_modules():
-    modlist = []
-    try:
-        fp = open('/proc/modules', 'r')
-        line = fp.readline()
-        while line:
-            mod = line.split()
-            modlist.append({"modname": mod[0],
-                            "modsize": mod[1],
-                            "numusers": mod[2],
-                            "usedby": mod[3],
-                            "modstate": mod[4]})
-            line = fp.readline()
-        fp.close()
-    except Exception, err:
-        raise err
-    return modlist
-
-
-if __name__ == "__main__":
-    print "\tRunning on %s" % get_base_os()
-    print "\tNUMA nodes: %d" % get_num_nodes()
-    print "\tMemory available: %03.2f %s" % get_memory_size()
-    (curr, avail) = get_clocksources()
-    print "\tCurrent clocksource: %s" % curr
-    print "\tAvailable clocksources: %s" % avail
-    print "\tModules:"
-    for m in get_modules():
-        print "\t\t%s: %s" % (m['modname'], m['modstate'])
-    
diff --git a/rteval/version.py b/rteval/version.py
new file mode 100644
index 0000000..fdc6b43
--- /dev/null
+++ b/rteval/version.py
@@ -0,0 +1,26 @@
+#
+#   Copyright 2015  Clark Williams <williams@redhat.com>
+#   Copyright 2012 - 2013   David Sommerseth <davids@redhat.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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#   For the avoidance of doubt the "preferred form" of this code is one which
+#   is in an open unpatent encumbered format. Where cryptographic key signing
+#   forms part of the process of creating an executable the information
+#   including keys needed to generate an equivalently functional executable
+#   are deemed to be part of the source code.
+#
+
+RTEVAL_VERSION = '2.14'
diff --git a/rteval/xmlout.py b/rteval/xmlout.py
index a9a9562..ddc2964 100644
--- a/rteval/xmlout.py
+++ b/rteval/xmlout.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
-#!/usr/bin/python -tt
 #
-#   Copyright 2009,2010   Clark Williams <williams@redhat.com>
+#   Copyright 2009 - 2013   Clark Williams <williams@redhat.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
@@ -13,9 +12,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
@@ -23,14 +22,39 @@
 #   including keys needed to generate an equivalently functional executable
 #   are deemed to be part of the source code.
 #
+
 import os
 import sys
 import libxml2
-import libxslt
+import lxml.etree
 import codecs
 import re
 from string import maketrans
 
+
+def convert_libxml2_to_lxml_doc(inxml):
+    "Converts a libxml2.xmlDoc into a lxml.etree document object"
+
+    if not isinstance(inxml, libxml2.xmlDoc):
+        raise TypeError('Function requires an libxml2.xmlDoc as input')
+
+    root = inxml.getRootElement()
+    ret = lxml.etree.XML(root.serialize('UTF-8'))
+    del root
+    return ret
+
+
+
+def convert_lxml_to_libxml2_nodes(inlxml):
+    "Converts a lxml.etree elements tree into a libxml2.xmlNode object"
+
+    if not isinstance(inlxml,lxml.etree._Element) and not isinstance(inlxml, lxml.etree._XSLTResultTree):
+        raise TypeError('Function requires an lxml.etree object as input')
+
+    return libxml2.parseDoc(lxml.etree.tostring(inlxml)).getRootElement()
+
+
+
 class XMLOut(object):
     '''Class to create XML output'''
     def __init__(self, roottag, version, attr = None, encoding='UTF-8'):
@@ -171,6 +195,7 @@
 
         self.status = 2 # Confirm that we have loaded a report from file
 
+
     def Write(self, filename, xslt = None):
         if self.status != 2 and self.status != 3:
             raise RuntimeError, "XMLOut: XML document is not closed"
@@ -181,8 +206,10 @@
             return
         else:
             # Load XSLT file and prepare the XSLT parser
-            xsltdoc = libxml2.parseFile(xslt)
-            parser = libxslt.parseStylesheetDoc(xsltdoc)
+            xsltfile = open(xslt, 'r')
+            xsltdoc = lxml.etree.parse(xsltfile)
+            parser = lxml.etree.XSLT(xsltdoc)
+            xsltfile.close()
 
             # imitate libxml2's filename interpretation
             if filename != "-":
@@ -192,18 +219,22 @@
             #
             # Parse XML+XSLT and write the result to file
             #
-            resdoc = parser.applyStylesheet(self.xmldoc, None)
-            # Decode the result string according to the charset declared in the XSLT file
-            xsltres = parser.saveResultToString(resdoc).decode(parser.encoding())
+            xmldoc = convert_libxml2_to_lxml_doc(self.xmldoc)
+            resdoc = parser(xmldoc)
+
             #  Write the file with the requested output encoding
-            dstfile.write(xsltres.encode(self.encoding))
+            dstfile.write(unicode(resdoc).encode(self.encoding))
 
             if dstfile != sys.stdout:
                 dstfile.close()
 
             # Clean up
-            resdoc.freeDoc()
-            xsltdoc.freeDoc()
+            del resdoc
+            del xsltdoc
+            del parser
+            del xsltfile
+            del xmldoc
+
 
     def GetXMLdocument(self):
         if self.status != 2 and self.status != 3:
diff --git a/server/COPYING b/server/COPYING
index 8a2927c..d159169 100644
--- a/server/COPYING
+++ b/server/COPYING
@@ -1,13 +1,12 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
 
-		    GNU GENERAL PUBLIC LICENSE
-		       Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-                          675 Mass Ave, Cambridge, MA 02139, USA
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
-			    Preamble
+                            Preamble
 
   The licenses for most software are designed to take away your
 freedom to share and change it.  By contrast, the GNU General Public
@@ -16,7 +15,7 @@
 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
+the GNU Lesser General Public License instead.)  You can apply it to
 your programs, too.
 
   When we speak of free software, we are referring to freedom, not
@@ -56,8 +55,8 @@
 
   The precise terms and conditions for copying, distribution and
 modification follow.
-
-		    GNU GENERAL PUBLIC LICENSE
+
+                    GNU GENERAL PUBLIC LICENSE
    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
   0. This License applies to any program or other work which contains
@@ -111,7 +110,7 @@
     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
@@ -169,7 +168,7 @@
 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
@@ -226,7 +225,7 @@
 
 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
@@ -256,7 +255,7 @@
 of preserving the free status of all derivatives of our free software and
 of promoting the sharing and reuse of software generally.
 
-			    NO WARRANTY
+                            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
@@ -278,9 +277,9 @@
 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
+                     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
@@ -292,7 +291,7 @@
 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) 19yy  <name of author>
+    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
@@ -304,16 +303,16 @@
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
 
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+    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.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) 19yy name of author
+    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.
@@ -336,489 +335,5 @@
 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
+library.  If this is what you want to do, use the GNU Lesser General
 Public License instead of this License.
-
----------------------------------------------------------------------------
-
-		  GNU LIBRARY GENERAL PUBLIC LICENSE
-		       Version 2, June 1991
-
- Copyright (C) 1991 Free Software Foundation, Inc.
-                    675 Mass Ave, Cambridge, MA 02139, USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-[This is the first released version of the library GPL.  It is
- numbered 2 because it goes with version 2 of the ordinary GPL.]
-
-			    Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
-
-  This license, the Library General Public License, applies to some
-specially designated Free Software Foundation software, and to any
-other libraries whose authors decide to use it.  You can use it for
-your libraries, 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 library, or if you modify it.
-
-  For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you.  You must make sure that they, too, receive or can get the source
-code.  If you link a program with the library, you must provide
-complete object files to the recipients so that they can relink them
-with the library, after making changes to the library and recompiling
-it.  And you must show them these terms so they know their rights.
-
-  Our method of protecting your rights has two steps: (1) copyright
-the library, and (2) offer you this license which gives you legal
-permission to copy, distribute and/or modify the library.
-
-  Also, for each distributor's protection, we want to make certain
-that everyone understands that there is no warranty for this free
-library.  If the library is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original
-version, 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 companies distributing free
-software will individually obtain patent licenses, thus in effect
-transforming the program into proprietary software.  To prevent this,
-we have made it clear that any patent must be licensed for everyone's
-free use or not licensed at all.
-
-  Most GNU software, including some libraries, is covered by the ordinary
-GNU General Public License, which was designed for utility programs.  This
-license, the GNU Library General Public License, applies to certain
-designated libraries.  This license is quite different from the ordinary
-one; be sure to read it in full, and don't assume that anything in it is
-the same as in the ordinary license.
-
-  The reason we have a separate public license for some libraries is that
-they blur the distinction we usually make between modifying or adding to a
-program and simply using it.  Linking a program with a library, without
-changing the library, is in some sense simply using the library, and is
-analogous to running a utility program or application program.  However, in
-a textual and legal sense, the linked executable is a combined work, a
-derivative of the original library, and the ordinary General Public License
-treats it as such.
-
-  Because of this blurred distinction, using the ordinary General
-Public License for libraries did not effectively promote software
-sharing, because most developers did not use the libraries.  We
-concluded that weaker conditions might promote sharing better.
-
-  However, unrestricted linking of non-free programs would deprive the
-users of those programs of all benefit from the free status of the
-libraries themselves.  This Library General Public License is intended to
-permit developers of non-free programs to use free libraries, while
-preserving your freedom as a user of such programs to change the free
-libraries that are incorporated in them.  (We have not seen how to achieve
-this as regards changes in header files, but we have achieved it as regards
-changes in the actual functions of the Library.)  The hope is that this
-will lead to faster development of free libraries.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.  Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library".  The
-former contains code derived from the library, while the latter only
-works together with the library.
-
-  Note that it is possible for a library to be covered by the ordinary
-General Public License rather than by this special one.
-
-		  GNU LIBRARY GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License Agreement applies to any software library which
-contains a notice placed by the copyright holder or other authorized
-party saying it may be distributed under the terms of this Library
-General Public License (also called "this License").  Each licensee is
-addressed as "you".
-
-  A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
-
-  The "Library", below, refers to any such software library or work
-which has been distributed under these terms.  A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language.  (Hereinafter, translation is
-included without limitation in the term "modification".)
-
-  "Source code" for a work means the preferred form of the work for
-making modifications to it.  For a library, 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 library.
-
-  Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it).  Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-  
-  1. You may copy and distribute verbatim copies of the Library's
-complete 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 distribute a copy of this License along with the
-Library.
-
-  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 Library or any portion
-of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
-
-    b) You must cause the files modified to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    c) You must cause the whole of the work to be licensed at no
-    charge to all third parties under the terms of this License.
-
-    d) If a facility in the modified Library refers to a function or a
-    table of data to be supplied by an application program that uses
-    the facility, other than as an argument passed when the facility
-    is invoked, then you must make a good faith effort to ensure that,
-    in the event an application does not supply such function or
-    table, the facility still operates, and performs whatever part of
-    its purpose remains meaningful.
-
-    (For example, a function in a library to compute square roots has
-    a purpose that is entirely well-defined independent of the
-    application.  Therefore, Subsection 2d requires that any
-    application-supplied function or table used by this function must
-    be optional: if the application does not supply it, the square
-    root function must still compute square roots.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Library,
-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 Library, 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 Library.
-
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library.  To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License.  (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.)  Do not make any other change in
-these notices.
-
-  Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
-
-  This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
-
-  4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you 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.
-
-  If distribution of 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 satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library".  Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
-
-  However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library".  The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
-  When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library.  The
-threshold for this to be true is not precisely defined by law.
-
-  If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work.  (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
-  Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
-
-  6. As an exception to the Sections above, you may also compile or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
-
-  You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License.  You must supply a copy of this License.  If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License.  Also, you must do one
-of these things:
-
-    a) Accompany the work with the complete corresponding
-    machine-readable source code for the Library including whatever
-    changes were used in the work (which must be distributed under
-    Sections 1 and 2 above); and, if the work is an executable linked
-    with the Library, with the complete machine-readable "work that
-    uses the Library", as object code and/or source code, so that the
-    user can modify the Library and then relink to produce a modified
-    executable containing the modified Library.  (It is understood
-    that the user who changes the contents of definitions files in the
-    Library will not necessarily be able to recompile the application
-    to use the modified definitions.)
-
-    b) Accompany the work with a written offer, valid for at
-    least three years, to give the same user the materials
-    specified in Subsection 6a, above, for a charge no more
-    than the cost of performing this distribution.
-
-    c) If distribution of the work is made by offering access to copy
-    from a designated place, offer equivalent access to copy the above
-    specified materials from the same place.
-
-    d) Verify that the user has already received a copy of these
-    materials or that you have already sent this user a copy.
-
-  For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it.  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.
-
-  It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system.  Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-
-  7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
-    a) Accompany the combined library with a copy of the same work
-    based on the Library, uncombined with any other library
-    facilities.  This must be distributed under the terms of the
-    Sections above.
-
-    b) Give prominent notice with the combined library of the fact
-    that part of it is a work based on the Library, and explaining
-    where to find the accompanying uncombined form of the same work.
-
-  8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License.  Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library 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.
-
-  9. 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 Library or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
-
-  10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-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.
-
-  11. 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 Library at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Library 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 Library.
-
-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.
-
-  12. If the distribution and/or use of the Library is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library 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.
-
-  13. The Free Software Foundation may publish revised and/or new
-versions of the Library 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 Library
-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 Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-
-  14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-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
-
-  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "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
-LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. 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 LIBRARY 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
-LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
-
-		     END OF TERMS AND CONDITIONS
-
-     Appendix: How to Apply These Terms to Your New Libraries
-
-  If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change.  You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms of the
-ordinary General Public License).
-
-  To apply these terms, attach the following notices to the library.  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 library's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This library is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Library General Public
-    License as published by the Free Software Foundation; either
-    version 2 of the License, or (at your option) any later version.
-
-    This library 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
-    Library General Public License for more details.
-
-    You should have received a copy of the GNU Library General Public
-    License along with this library; if not, write to the Free
-    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the library, if
-necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the
-  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
-
-  <signature of Ty Coon>, 1 April 1990
-  Ty Coon, President of Vice
-
-That's all there is to it!
diff --git a/server/Makefile.am b/server/Makefile.am
index bf56ac4..fb707cc 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -1,6 +1,6 @@
 #   Makefile.am - autotools configuration file
 #
-#   Copyright 2009-2011   David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -12,9 +12,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/server/apache-rteval-wsgi.conf.tpl b/server/apache-rteval-wsgi.conf.tpl
index 9bf5fea..ab5d59f 100644
--- a/server/apache-rteval-wsgi.conf.tpl
+++ b/server/apache-rteval-wsgi.conf.tpl
@@ -2,7 +2,7 @@
 #
 # Apache config entry to enable the rteval XML-RPC server
 #
-#   Copyright 2011      David Sommerseth <davids@redhat.com>
+#   Copyright 2011 - 2013   David Sommerseth <davids@redhat.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
@@ -14,9 +14,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/server/apache-rteval.conf.tpl b/server/apache-rteval.conf.tpl
index 1b90be8..18eca42 100644
--- a/server/apache-rteval.conf.tpl
+++ b/server/apache-rteval.conf.tpl
@@ -2,7 +2,7 @@
 #
 # Apache config entry to enable the rteval XML-RPC server
 #
-#   Copyright 2009      David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -14,9 +14,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/server/configure.ac b/server/configure.ac
index d0721c3..9b5868f 100644
--- a/server/configure.ac
+++ b/server/configure.ac
@@ -1,6 +1,6 @@
 #   configure.ac - autotools configuration file
 #
-#   Copyright 2009-2011   David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -12,9 +12,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
@@ -22,11 +22,13 @@
 #   including keys needed to generate an equivalently functional executable
 #   are deemed to be part of the source code.
 #
+#
+#
 #   To create the ./configure script you need to run 'autoreconf --install'
 #
 
-AC_INIT([rteval-xmlrpc], [1.5], [davids@redhat.com])
-SQLSCHEMAVER=1.4
+AC_INIT([rteval-xmlrpc], [1.6], [davids@redhat.com])
+SQLSCHEMAVER=1.5
 AC_SUBST(SQLSCHEMAVER)
 
 AM_INIT_AUTOMAKE([-Wall -Werror foreign])
@@ -83,6 +85,7 @@
 else
    AC_SUBST([LIBXSLT_INC], [$(xslt-config --cflags)])
    CPPFLAGS="$CPPFLAGS $LIBXSLT_INC"
+   LDFLAGS="$LDFLAGS -lexslt"
 fi
 AC_CHECK_HEADERS([libxslt/xsltInternals.h])
 AC_CHECK_HEADERS([libxslt/transform.h])
@@ -92,6 +95,7 @@
 AC_CHECK_LIB([xslt], [xsltParseStylesheetFile], [DUMMY=], AX_msgMISSINGFUNC())
 AC_CHECK_LIB([xslt], [xsltApplyStylesheet], [DUMMY=], AX_msgMISSINGFUNC())
 AC_CHECK_LIB([xslt], [xsltFreeStylesheet], [DUMMY=], AX_msgMISSINGFUNC())
+AC_CHECK_LIB([exslt], [exsltRegisterAll], [DUMMY=], AX_msgMISSINGFUNC())
 
 # Check for libpq
 AC_CHECK_PROGS([PGSQLCFG], [pg_config], [:])
@@ -139,5 +143,7 @@
 AC_CONFIG_SRCDIR([parser/rteval-parserd.c])
 AC_CONFIG_HEADERS([parser/config.h])
 AC_CONFIG_FILES([Makefile parser/Makefile])
+AC_SUBST([AM_CXXFLAGS], [$CFLAGS])
+AC_SUBST([AM_LDFLAGS], [$LDFLAGS])
 
 AC_OUTPUT
diff --git a/server/database.py b/server/database.py
index c749593..57ba6e9 100644
--- a/server/database.py
+++ b/server/database.py
@@ -3,7 +3,7 @@
 #   Library for processing results from XMLSQLparser and
 #   query a PostgreSQL database based on the input data
 #
-#   Copyright 2009      David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -15,9 +15,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/server/parser/Makefile.am b/server/parser/Makefile.am
index d328162..f7836cc 100644
--- a/server/parser/Makefile.am
+++ b/server/parser/Makefile.am
@@ -1,6 +1,6 @@
 #   Makefile.am - autotools configuration file
 #
-#   Copyright 2009   David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -12,9 +12,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
@@ -24,7 +24,7 @@
 #
 
 # Generic include files, found by ./configure
-AM_CPPFLAGS = $(LIBXML2_INC) $(LIBXSLT_INC) $(LIBPQ_INC)
+AM_CPPFLAGS = $(LIBXML2_INC) $(LIBXSLT_INC) $(LIBPQ_INC) -std=gnu89
 
 # What is required to build rteval_parserd
 bin_PROGRAMS = rteval-parserd
diff --git a/server/parser/README.parser b/server/parser/README.parser
index 885727c..2618552 100644
--- a/server/parser/README.parser
+++ b/server/parser/README.parser
@@ -98,6 +98,13 @@
     many worker threads, your system might become unresponsive for a while
     and the parser might be killed by the kernel (OOM).
 
+  - measurement_tables: cyclic_statistics, cyclic_histogram, hwlatdetect_summary, hwlatdetect_samples
+    Declares which measurement results will be parsed and stored in the
+    database.  These names are referring to table definitions in the
+    xmlparser.xsl XSLT template.  The definitions in this template tells
+    rteval-parsed which data to extract from the rteval summary.xml report
+    and where and how to store it in the database.
+
 
 ** rteval-parserd arguments
 
diff --git a/server/parser/configparser.c b/server/parser/configparser.c
index 3cad827..3b95c20 100644
--- a/server/parser/configparser.c
+++ b/server/parser/configparser.c
@@ -119,6 +119,7 @@
 	eAdd_value(cfg, "db_password", "rtevaldb_parser");
 	eAdd_value(cfg, "reportdir", "/var/lib/rteval/reports");
 	eAdd_value(cfg, "max_report_size", "2097152"); // 2MB
+	eAdd_value(cfg, "measurement_tables", "cyclic_statistics, cyclic_histogram, hwlatdetect_summary, hwlatdetect_samples");
 
 	// Copy over the arguments to the config, update existing settings
 	for( ptr = prgargs; ptr; ptr = ptr->next ) {
diff --git a/server/parser/parsethread.c b/server/parser/parsethread.c
index 7144695..8a40427 100644
--- a/server/parser/parsethread.c
+++ b/server/parser/parsethread.c
@@ -177,7 +177,7 @@
  *          STAT_RTERIDREG: Failed to get a new rterid value
  *          STAT_GENDB    : Failed to start an SQL transaction (BEGIN)
  *          STAT_RTEVRUNS : Failed to register the rteval run into rtevalruns or rtevalruns_details
- *          STAT_CYCLIC   : Failed to register the data into cyclic_statistics or cyclic_rawdata tables
+ *          STAT_MEASURE  : Failed to register the measurement data into tables their corresponding tables
  *          STAT_REPMOVE  : Failed to move the report file
  * @endcode
  */
@@ -212,15 +212,18 @@
 			 "[Thread %i] Failed to register system (submid: %i, XML file: %s)",
 			 thrdata->id, job->submid, job->filename);
 		rc = STAT_SYSREG;
+		pthread_mutex_unlock(thrdata->mtx_sysreg);
 		goto exit;
 
 	}
+
 	rterid = db_get_new_rterid(thrdata->dbc);
 	if( rterid < 0 ) {
 		writelog(thrdata->dbc->log, LOG_ERR,
 			 "[Thread %i] Failed to register rteval run (submid: %i, XML file: %s)",
 			 thrdata->id, job->submid, job->filename);
 		rc = STAT_RTERIDREG;
+		pthread_mutex_unlock(thrdata->mtx_sysreg);
 		goto exit;
 	}
 	pthread_mutex_unlock(thrdata->mtx_sysreg);
@@ -251,12 +254,12 @@
 		goto exit;
 	}
 
-	if( db_register_cyclictest(thrdata->dbc, thrdata->xslt, repxml, rterid) != 1 ) {
+	if( db_register_measurements(thrdata->dbc, thrdata->xslt, repxml, rterid) != 1 ) {
 		writelog(thrdata->dbc->log, LOG_ERR,
-			 "[Thread %i] Failed to register cyclictest data (submid: %i, XML file: %s)",
+			 "[Thread %i] Failed to register measurement data (submid: %i, XML file: %s)",
 			 thrdata->id, job->submid, job->filename);
 		db_rollback(thrdata->dbc);
-		rc = STAT_CYCLIC;
+		rc = STAT_MEASURE;
 		goto exit;
 	}
 
diff --git a/server/parser/pgsql.c b/server/parser/pgsql.c
index 8ea5d08..adf97c9 100644
--- a/server/parser/pgsql.c
+++ b/server/parser/pgsql.c
@@ -747,7 +747,7 @@
 	case STAT_SYSREG:
 	case STAT_GENDB:
 	case STAT_RTEVRUNS:
-	case STAT_CYCLIC:
+	case STAT_MEASURE:
 		snprintf(sql, 4096,
 			 "UPDATE submissionqueue SET status = %i, parseend = NOW() WHERE submid = %i",
 			 status, submid);
@@ -878,7 +878,7 @@
 			append_str(sqlq, ipaddr, 4092);
 			append_str(sqlq, "'", 4096);
 		} else {
-			append_str(sqlq, "%s AND ipaddr IS NULL", 4096);
+			append_str(sqlq, "AND ipaddr IS NULL", 4096);
 		}
 
 		dbres = PQexec(dbc->db, sqlq);
@@ -1041,53 +1041,54 @@
 
 
 /**
- * Registers data returned from cyclictest into the database.
+ * Registers data returned from measurement results into the database.
  *
- * @param dbc      Database handler where to perform the SQL queries
+ * @param dbc        Database handler where to perform the SQL queries
  * @param xslt       A pointer to a parsed 'xmlparser.xsl' XSLT template
  * @param summaryxml The XML report from rteval
  * @param rterid     A positive integer referencing the rteval run ID, returned from db_register_rtevalrun()
  *
  * @return Returns 1 on success, otherwise -1
  */
-int db_register_cyclictest(dbconn *dbc, xsltStylesheet *xslt, xmlDoc *summaryxml, int rterid) {
+int db_register_measurements(dbconn *dbc, xsltStylesheet *xslt, xmlDoc *summaryxml, int rterid) {
 	int result = -1;
-	xmlDoc *cyclic_d = NULL;
+	xmlDoc *meas_d = NULL;
 	parseParams prms;
 	eurephiaVALUES *dbdata = NULL;
-	int cyclicdata = 0;
-	const char *cyclictables[] = { "cyclic_statistics", "cyclic_histogram", "cyclic_rawdata", NULL };
+	int measrecs = 0;
+        char *tbl = NULL;
 	int i;
 
 	memset(&prms, 0, sizeof(parseParams));
 	prms.rterid = rterid;
 
-	// Register the cyclictest data
-	for( i = 0; cyclictables[i]; i++ ) {
-		prms.table = cyclictables[i];
-		cyclic_d = parseToSQLdata(dbc->log, xslt, summaryxml, &prms);
-		if( cyclic_d && cyclic_d->children ) {
+	// Loop through all configured measurement tables and process each table
+        i = 0;
+        for_array_str(tbl, i, dbc->measurement_tbls) {
+                writelog(dbc->log, LOG_DEBUG, "Processing measurement table '%s'", tbl);
+		prms.table = tbl;
+		meas_d = parseToSQLdata(dbc->log, xslt, summaryxml, &prms);
+		if( meas_d && meas_d->children ) {
 			// Insert SQL data which was found and generated
-			dbdata = pgsql_INSERT(dbc, cyclic_d);
+			dbdata = pgsql_INSERT(dbc, meas_d);
 			if( !dbdata ) {
 				result = -1;
-				xmlFreeDoc(cyclic_d);
+				xmlFreeDoc(meas_d);
 				goto exit;
 			}
 
 			if (eCount(dbdata) > 0) {
-				cyclicdata++;
+				measrecs++;
 			}
 			eFree_values(dbdata);
-			cyclicdata = 1;
 		}
-		if( cyclic_d ) {
-			xmlFreeDoc(cyclic_d);
+		if( meas_d ) {
+			xmlFreeDoc(meas_d);
 		}
 	}
 
 	// Report error if not enough cyclictest data is registered.
-	if( cyclicdata > 1 ) {
+	if( measrecs < 1 ) {
 		writelog(dbc->log, LOG_ALERT,
 			 "[Connection %i] No cyclictest raw data or histogram data registered", dbc->id);
 		result = -1;
diff --git a/server/parser/pgsql.h b/server/parser/pgsql.h
index 5248cc9..e122b5e 100644
--- a/server/parser/pgsql.h
+++ b/server/parser/pgsql.h
@@ -31,6 +31,7 @@
 #include <log.h>
 #include <eurephia_values.h>
 #include <parsethread.h>
+#include <xmlparser.h>
 
 /**
  *  A unified database abstraction layer, providing log support
@@ -40,6 +41,7 @@
 	LogContext *log;           /**< Initialised log context */
 	PGconn *db;                /**< Database connection handler */
 	unsigned int sqlschemaver; /**< SQL schema version, retrieved from rteval_info table */
+	array_str_t *measurement_tbls; /**< Measurement tables to process */
 } dbconn;
 
 /* Generic database function */
@@ -58,6 +60,6 @@
 int db_get_new_rterid(dbconn *dbc);
 int db_register_rtevalrun(dbconn *dbc, xsltStylesheet *xslt, xmlDoc *summaryxml,
 			  unsigned int submid, int syskey, int rterid, const char *report_fname);
-int db_register_cyclictest(dbconn *dbc, xsltStylesheet *xslt, xmlDoc *summaryxml, int rterid);
+int db_register_measurements(dbconn *dbc, xsltStylesheet *xslt, xmlDoc *summaryxml, int rterid);
 
 #endif
diff --git a/server/parser/rteval-parserd.c b/server/parser/rteval-parserd.c
index 878e710..58bfb0e 100644
--- a/server/parser/rteval-parserd.c
+++ b/server/parser/rteval-parserd.c
@@ -409,6 +409,15 @@
 			goto exit;
 		}
 
+		// Parse the measurement_tables config variable, split it up into an array
+		thrdata[i]->dbc->measurement_tbls = strSplit(eGet_value(config, "measurement_tables"), ", ");
+		if( !thrdata[i]->dbc->measurement_tbls ) {
+			writelog(dbc->log, LOG_CRIT, "Failed to parse measurement_tables configuration");
+			rc = 2;
+			shutdown = 1;
+			goto exit;
+		}
+
 		thrdata[i]->shutdown = &shutdown;
 		thrdata[i]->threadcount = &activethreads;
 		thrdata[i]->mtx_thrcnt = &mtx_thrcnt;
@@ -492,6 +501,7 @@
 
 		// Disconnect threads database connection
 		if( thrdata && thrdata[i] ) {
+			strFree(thrdata[i]->dbc->measurement_tbls);
 			db_disconnect(thrdata[i]->dbc);
 			free_nullsafe(thrdata[i]);
 		}
diff --git a/server/parser/statuses.h b/server/parser/statuses.h
index 1320c17..0ac0ac6 100644
--- a/server/parser/statuses.h
+++ b/server/parser/statuses.h
@@ -33,7 +33,7 @@
 #define STAT_RTERIDREG 7         /**< Failed to get a new rterid value for the rteval run */
 #define STAT_GENDB     8         /**< General database error */
 #define STAT_RTEVRUNS  9         /**< Registering rteval run information failed */
-#define STAT_CYCLIC    10        /**< Registering cyclictest results failed */
+#define STAT_MEASURE   10        /**< Registering measurement results failed */
 #define STAT_REPMOVE   11        /**< Failed to move the report file */
 #define STAT_FTOOBIG   12        /**< Report is too big (see config parameter: max_report_size) */
 
diff --git a/server/parser/xmlparser.c b/server/parser/xmlparser.c
index 0ff6e72..2971600 100644
--- a/server/parser/xmlparser.c
+++ b/server/parser/xmlparser.c
@@ -35,6 +35,7 @@
 #include <libxslt/xsltInternals.h>
 #include <libxslt/transform.h>
 #include <libxslt/xsltutils.h>
+#include <libexslt/exslt.h>
 
 #include <eurephia_nullsafe.h>
 #include <eurephia_xml.h>
@@ -103,12 +104,102 @@
     return *ptr == '\0';
 }
 
+
+/**
+ * Split a string into an array.
+ * Use strGet() to extract the results and strFree() to free the memory
+ * allocated by strSplit()
+ *
+ * @param str Input string
+ * @param sep Separator list (string)
+ *
+ * @returns Returns a pointer to a array_str_t struct on success, otherwise NULL
+ */
+array_str_t * strSplit(const char * str, const char * sep)
+{
+        array_str_t * ret = calloc(1, sizeof(array_str_t));
+        char * p = NULL, *cp = strdup(str);
+
+        if( !ret || !cp ) {
+                fprintf(stderr, "Memory allocation error in strSplit()\n");
+                return NULL;
+        }
+
+        p = strtok(cp, sep);
+        while( p ) {
+                ret->data = realloc(ret->data, sizeof(char *) * ++(ret->size));
+                if( !ret->data ) {
+                        fprintf(stderr, "Memory allocation error in strSplit() while parsing\n");
+                        free(ret);
+                        return NULL;
+                }
+                ret->data[ret->size-1] = strdup(p);
+                p = strtok(NULL, sep);
+        }
+        free(cp);
+        return ret;
+}
+
+/**
+ * Retrieve an array memeber
+ *
+ * @param ar Pointer holding the array_str_t data
+ * @param el Element number to retrive
+ *
+ * @returns Returns a pointer to the array member.  This value must never be freed.
+ * On failure NULL is returned.  Only possible failures are out-of-range or a NULL
+ * array input.
+ */
+inline char * strGet(array_str_t * ar, unsigned int el)
+{
+        return (!ar && (el > ar->size) ? NULL : ar->data[el]);
+}
+
+/**
+ * Retrive number of accessible array members
+ *
+ * @param ar Pointer holding the array_str_t data
+ *
+ * @returns Returns the number of elements in the array_str_t struct.
+ *          If a NULL pointer is given, it will return 0.
+ */
+inline unsigned int strSize(array_str_t * ar)
+{
+        return (!ar ? 0 : ar->size);
+}
+
+/**
+ * Frees up the memory held by an array_str_t struct.
+ *
+ * @params ar Pointer holding the array_str_t data to be freed
+ *
+ */
+void strFree(array_str_t * ar)
+{
+        int i = 0;
+
+        if( !ar ) {
+                return;
+        }
+
+        for( i = 0; i < ar->size; i++ ) {
+                free(ar->data[i]);
+                ar->data[i] = NULL;
+        }
+        free(ar->data); ar->data = NULL;
+        free(ar);
+}
+
 /**
  * Initialise the XML parser, setting some global variables
  */
 void init_xmlparser(dbhelper_func const * dbhelpers)
 {
 	xmlparser_dbhelpers = dbhelpers;
+
+        /* Init libxml2 and load all exslt functions */
+        xmlInitMemory();
+        exsltRegisterAll();
 }
 
 
@@ -125,10 +216,12 @@
  */
 xmlDoc *parseToSQLdata(LogContext *log, xsltStylesheet *xslt, xmlDoc *indata_d, parseParams *params) {
         xmlDoc *result_d = NULL;
-        char *xsltparams[10];
+        char **xsltparams = NULL;
         unsigned int idx = 0, idx_table = 0, idx_submid = 0,
 		idx_syskey = 0, idx_rterid = 0, idx_repfname = 0;
 
+        xsltparams = calloc(10, sizeof(char *));
+
         if( xmlparser_dbhelpers == NULL ) {
                 writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised");
                 return NULL;
@@ -190,6 +283,7 @@
                 free(xsltparams[idx_repfname]);
         }
 
+        free(xsltparams);
         return result_d;
 }
 
diff --git a/server/parser/xmlparser.h b/server/parser/xmlparser.h
index 79c79a1..cd7eb63 100644
--- a/server/parser/xmlparser.h
+++ b/server/parser/xmlparser.h
@@ -39,6 +39,28 @@
         unsigned int rterid;          /**< References rtevalruns.rterid */
 } parseParams;
 
+/**
+ * Container for string arrays
+ */
+typedef struct {
+        unsigned int size;
+        char **data;
+} array_str_t;
+
+array_str_t * strSplit(const char * str, const char * sep);
+inline char * strGet(array_str_t * ar, unsigned int el);
+inline unsigned int strSize(array_str_t * ar);
+void strFree(array_str_t * ar);
+
+/** Simple for-loop iterator for array_str_t objects
+ *
+ * @param ptr Return pointer (char *) where the element data is returned
+ * @param idx Element index counter, declares where it should start and can be used
+ *            to track the iteration process.  Must be an int variable
+ * @param ar  The array_str_t object to iterate
+ */
+#define for_array_str(ptr, idx, ar) for( ptr = ar->data[idx]; idx++ < ar->size; \
+                                         ptr=(idx < ar->size ? ar->data[idx] : NULL) )
 
 /**
  *  Database specific helper functions
diff --git a/server/parser/xmlparser.xsl b/server/parser/xmlparser.xsl
index 2ee9370..58f2d15 100644
--- a/server/parser/xmlparser.xsl
+++ b/server/parser/xmlparser.xsl
@@ -20,221 +20,49 @@
      *
 -->
 
-<xsl:stylesheet  version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:stylesheet  version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                 xmlns:math="http://exslt.org/math"
+                 extension-element-prefixes="math">
   <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
 
   <!-- Used for iterating CPU topology information -->
   <xsl:key name="pkgkey" match="cpu" use="@physical_package_id"/>
 
-  <xsl:template match="/rteval">
+
+  <!-- Supported tables in reports in rteval v1.37 and older -->
+  <xsl:template match="/rteval['2.0' > @version]">
     <xsl:choose>
       <!-- TABLE: systems -->
       <xsl:when test="$table = 'systems'">
-	<sqldata schemaver="1.0" table="systems" key="syskey">
-	  <fields>
-            <field fid="0">sysid</field>
-            <field fid="1">dmidata</field>
-	  </fields>
-	  <records>
-            <record>
-              <value fid="0" hash="sha1">
-		<xsl:value-of select="concat(HardwareInfo/@SystemUUID,':',HardwareInfo/@SerialNo)"/>
-              </value>
-              <value fid="1" type="xmlblob">
-		<xsl:copy-of select="HardwareInfo"/>
-              </value>
-            </record>
-	  </records>
-	</sqldata>
+        <xsl:apply-templates select="HardwareInfo" mode="tbl_systems"/>
       </xsl:when>
 
       <!-- TABLE: systems_hostname -->
       <xsl:when test="$table = 'systems_hostname'">
-        <xsl:if test="string(number($syskey)) = 'NaN'">
-          <xsl:message terminate="yes">
-            <xsl:text>Invalid 'syskey' parameter value: </xsl:text><xsl:value-of select="syskey"/>
-          </xsl:message>
-        </xsl:if>
-	<sqldata schemaver="1.0" table="systems_hostname">
-	  <fields>
-            <field fid="0">syskey</field>
-            <field fid="1">hostname</field>
-            <field fid="2">ipaddr</field>
-	  </fields>
-	  <records>
-            <record>
-              <value fid="0"><xsl:value-of select="$syskey"/></value>
-              <value fid="1"><xsl:value-of select="uname/node"/></value>
-              <value fid="2"><xsl:value-of select="network_config/interface/IPv4[@defaultgw=1]/@ipaddr"/></value>
-            </record>
-	  </records>
-	</sqldata>
+        <xsl:apply-templates select="." mode="tbl_systems_hostname"/>
       </xsl:when>
 
       <!-- TABLE: rtevalruns -->
       <xsl:when test="$table = 'rtevalruns'">
-        <xsl:if test="string(number($syskey)) = 'NaN'">
-          <xsl:message terminate="yes">
-            <xsl:text>Invalid 'syskey' parameter value: </xsl:text><xsl:value-of select="syskey"/>
-          </xsl:message>
-        </xsl:if>
-        <xsl:if test="string(number($rterid)) = 'NaN'">
-          <xsl:message terminate="yes">
-            <xsl:text>Invalid rterid' parameter value: </xsl:text><xsl:value-of select="$rterid"/>
-          </xsl:message>
-        </xsl:if>
-        <xsl:if test="$report_filename = ''">
-          <xsl:message terminate="yes">
-            <xsl:text>The parameter 'report_filename' parameter cannot be empty</xsl:text>
-          </xsl:message>
-        </xsl:if>
-	<sqldata schemaver="1.2" table="rtevalruns">
-	  <fields>
-            <field fid="0">syskey</field>
-            <field fid="1">kernel_ver</field>
-            <field fid="2">kernel_rt</field>
-            <field fid="3">arch</field>
-            <field fid="4">run_start</field>
-            <field fid="5">run_duration</field>
-            <field fid="6">load_avg</field>
-            <field fid="7">version</field>
-            <field fid="8">report_filename</field>
-	    <field fid="9">rterid</field>
-	    <field fid="10">submid</field>
-	    <field fid="11">distro</field>
-	  </fields>
-	  <records>
-            <record>
-              <value fid="0"><xsl:value-of select="$syskey"/></value>
-              <value fid="1"><xsl:value-of select="uname/kernel"/></value>
-              <value fid="2"><xsl:choose>
-		  <xsl:when test="uname/kernel/@is_RT = '1'">true</xsl:when>
-		  <xsl:otherwise>false</xsl:otherwise></xsl:choose>
-              </value>
-              <value fid="3"><xsl:value-of select="uname/arch"/></value>
-              <value fid="4"><xsl:value-of select="concat(run_info/date, ' ', run_info/time)"/></value>
-              <value fid="5">
-		<xsl:value-of select="(run_info/@days*86400)+(run_info/@hours*3600)
-                                      +(run_info/@minutes*60)+(run_info/@seconds)"/>
-              </value>
-              <value fid="6"><xsl:value-of select="loads/@load_average"/></value>
-              <value fid="7"><xsl:value-of select="@version"/></value>
-              <value fid="8"><xsl:value-of select="$report_filename"/></value>
-              <value fid="9"><xsl:value-of select="$rterid"/></value>
-              <value fid="10"><xsl:value-of select="$submid"/></value>
-	      <value fid="11"><xsl:value-of select="uname/baseos"/></value>
-            </record>
-	  </records>
-	</sqldata>
+        <xsl:apply-templates select="." mode="tbl_rtevalruns"/>
       </xsl:when>
 
       <!-- TABLE: rtevalruns_details -->
       <xsl:when test="$table = 'rtevalruns_details'">
-        <xsl:if test="string(number($rterid)) = 'NaN'">
-          <xsl:message terminate="yes">
-            <xsl:text>Invalid 'rterid' parameter value: </xsl:text><xsl:value-of select="$rterid"/>
-          </xsl:message>
-        </xsl:if>
-        <sqldata schemaver="1.4" table="rtevalruns_details">
-          <fields>
-            <field fid="0">rterid</field>
-            <field fid="1">numa_nodes</field>
-            <field fid="2">num_cpu_cores</field>
-            <field fid="3">num_cpu_sockets</field>
-            <field fid="4">xmldata</field>
-            <field fid="5">annotation</field>
-            <field fid="6">cpu_core_spread</field>
-          </fields>
-          <records>
-            <record>
-              <value fid="0"><xsl:value-of select="$rterid"/></value>
-              <value fid="1"><xsl:value-of select="hardware/numa_nodes"/></value>
-              <value fid="2">
-                <xsl:choose>
-                  <xsl:when test="hardware/cpu_topology">
-                    <xsl:value-of select="hardware/cpu_topology/@num_cpu_cores"/>
-                  </xsl:when>
-                  <xsl:otherwise>
-                    <xsl:value-of select="hardware/cpu_cores"/>
-                  </xsl:otherwise>
-                </xsl:choose>
-              </value>
-              <value fid="3">
-                <xsl:value-of select="hardware/cpu_topology/@num_cpu_sockets"/>
-              </value>
-              <value fid="4" type="xmlblob">
-                <rteval_details>
-                  <xsl:copy-of select="clocksource|services|kthreads|network_config|loads|cyclictest/command_line"/>
-                  <hardware>
-                    <xsl:copy-of select="hardware/memory_size|hardware/cpu_topology"/>
-                  </hardware>
-                </rteval_details>
-              </value>
-              <value fid="5"><xsl:value-of select="run_info/annotate"/></value>
-              <value fid="6" type="array">
-                <xsl:for-each select="hardware/cpu_topology/cpu[generate-id() = generate-id(key('pkgkey', @physical_package_id)[1])]">
-                  <xsl:call-template name="count_core_spread">
-                    <xsl:with-param name="pkgid" select="@physical_package_id"/>
-                  </xsl:call-template>
-                </xsl:for-each>
-              </value>
-            </record>
-          </records>
-        </sqldata>
+        <xsl:apply-templates select="." mode="tbl_rtevalruns_details"/>
       </xsl:when>
 
       <!-- TABLE: cyclic_statistics -->
       <xsl:when test="$table = 'cyclic_statistics'">
-        <xsl:if test="string(number($rterid)) = 'NaN'">
-          <xsl:message terminate="yes">
-            <xsl:text>Invalid 'rterid' parameter value: </xsl:text><xsl:value-of select="$rterid"/>
-          </xsl:message>
-        </xsl:if>
-	<sqldata schemaver="1.1" table="cyclic_statistics">
-	  <fields>
-            <field fid="0">rterid</field>
-            <field fid="1">coreid</field>
-            <field fid="2">priority</field>
-            <field fid="3">num_samples</field>
-            <field fid="4">lat_min</field>
-            <field fid="5">lat_max</field>
-            <field fid="6">lat_mean</field>
-            <field fid="7">mode</field>
-            <field fid="8">range</field>
-            <field fid="9">median</field>
-            <field fid="10">stddev</field>
-	    <field fid="11">mean_abs_dev</field>
-	    <field fid="12">variance</field>
-	  </fields>
-	  <records>
-            <xsl:for-each select="cyclictest/core/statistics|cyclictest/system/statistics">
-              <record>
-		<value fid="0"><xsl:value-of select="$rterid"/></value>
-		<value fid="1"><xsl:choose>
-		    <xsl:when test="../@id"><xsl:value-of select="../@id"/></xsl:when>
-		    <xsl:otherwise><xsl:attribute name="isnull">1</xsl:attribute></xsl:otherwise></xsl:choose>
-		</value>
-		<value fid="2"><xsl:choose>
-		    <xsl:when test="../@priority"><xsl:value-of select="../@priority"/></xsl:when>
-		    <xsl:otherwise><xsl:attribute name="isnull">1</xsl:attribute></xsl:otherwise></xsl:choose>
-		</value>
-		<value fid="3"><xsl:value-of select="samples"/></value>
-		<value fid="4"><xsl:value-of select="minimum"/></value>
-		<value fid="5"><xsl:value-of select="maximum"/></value>
-		<value fid="6"><xsl:value-of select="median"/></value>
-		<value fid="7"><xsl:value-of select="mode"/></value>
-		<value fid="8"><xsl:value-of select="range"/></value>
-		<value fid="9"><xsl:value-of select="mean"/></value>
-		<value fid="10"><xsl:value-of select="standard_deviation"/></value>
-		<value fid="11"><xsl:value-of select="mean_absolute_deviation"/></value>
-		<value fid="12"><xsl:value-of select="variance"/></value>
-              </record>
-            </xsl:for-each>
-	  </records>
-	</sqldata>
+        <xsl:apply-templates select="cyclictest" mode="tbl_cyclictest_statistics"/>
       </xsl:when>
 
-      <!-- TABLE: cyclic_rawdata -->
+      <!-- TABLE: cyclic_histogram -->
+      <xsl:when test="$table = 'cyclic_histogram'">
+        <xsl:apply-templates select="cyclictest" mode="tbl_cyclictest_histogram"/>
+      </xsl:when>
+
+      <!-- TABLE: cyclic_rawdata - only used by rteval v1.4 and earlier -->
       <xsl:when test="$table = 'cyclic_rawdata'">
         <xsl:if test="string(number($rterid)) = 'NaN'">
           <xsl:message terminate="yes">
@@ -261,27 +89,10 @@
 	</sqldata>
       </xsl:when>
 
-      <!-- TABLE: cyclic_histogram -->
-      <xsl:when test="$table = 'cyclic_histogram'">
-        <xsl:if test="string(number($rterid)) = 'NaN'">
-          <xsl:message terminate="yes">
-            <xsl:text>Invalid 'rterid' parameter value: </xsl:text><xsl:value-of select="$rterid"/>
-          </xsl:message>
-        </xsl:if>
-	<sqldata schemaver="1.0" table="cyclic_histogram">
-	  <fields>
-            <field fid="0">rterid</field>
-            <field fid="1">core</field>
-            <field fid="2">index</field>
-            <field fid="3">value</field>
-	  </fields>
-	  <records>
-            <xsl:apply-templates select="/rteval/cyclictest/system/histogram/bucket"
-				 mode="cyclic_histogram_rec_sql"/>
-            <xsl:apply-templates select="/rteval/cyclictest/core/histogram/bucket"
-				 mode="cyclic_histogram_rec_sql"/>
-	  </records>
-	</sqldata>
+      <!-- Tables only used by rteval v2.0 and newer - just ignore them to avoid parsing warnings -->
+      <xsl:when test="$table = 'hwlatdetect_summary'">
+      </xsl:when>
+      <xsl:when test="$table = 'hwlatdetect_samples'">
       </xsl:when>
 
       <xsl:otherwise>
@@ -292,7 +103,417 @@
     </xsl:choose>
   </xsl:template>
 
-  <xsl:template match="/rteval/cyclictest/system/histogram/bucket|/rteval/cyclictest/core/histogram/bucket"
+
+  <!-- Supported tables in reports from rteval v2.0 and newer -->
+  <xsl:template match="/rteval[@version >= '2.0']">
+    <xsl:choose>
+      <!-- TABLE: systems -->
+      <xsl:when test="$table = 'systems'">
+        <xsl:apply-templates select="SystemInfo/DMIinfo/HardwareInfo" mode="tbl_systems"/>
+      </xsl:when>
+
+      <!-- TABLE: systems_hostname -->
+      <xsl:when test="$table = 'systems_hostname'">
+        <xsl:apply-templates select="SystemInfo" mode="tbl_systems_hostname"/>
+      </xsl:when>
+
+      <!-- TABLE: rtevalruns -->
+      <xsl:when test="$table = 'rtevalruns'">
+        <xsl:apply-templates select="." mode="tbl_rtevalruns"/>
+      </xsl:when>
+
+      <!-- TABLE: rtevalruns_details -->
+      <xsl:when test="$table = 'rtevalruns_details'">
+        <xsl:apply-templates select="SystemInfo" mode="tbl_rtevalruns_details"/>
+      </xsl:when>
+
+      <!-- TABLE: cyclic_statistics -->
+      <xsl:when test="$table = 'cyclic_statistics'">
+        <xsl:apply-templates select="Measurements/Profile/cyclictest" mode="tbl_cyclictest_statistics"/>
+      </xsl:when>
+
+      <!-- TABLE: cyclic_histogram -->
+      <xsl:when test="$table = 'cyclic_histogram'">
+        <xsl:apply-templates select="Measurements/Profile/cyclictest" mode="tbl_cyclictest_histogram"/>
+      </xsl:when>
+
+      <!-- TABLE: hwlatdetect_summary -->
+      <xsl:when test="$table = 'hwlatdetect_summary'">
+        <xsl:apply-templates select="Measurements/Profile/hwlatdetect" mode="tbl_hwlatdetect_summary"/>
+      </xsl:when>
+
+      <!-- TABLE: hwlatdetect_samples -->
+      <xsl:when test="$table = 'hwlatdetect_samples'">
+        <xsl:apply-templates select="Measurements/Profile/hwlatdetect/samples" mode="tbl_hwlatdetect_samples"/>
+      </xsl:when>
+
+      <!-- TABLE: cyclic_rawdata - only used by rteval v1.4 and earlier -->
+      <xsl:when test="$table = 'cyclic_rawdata'"> <!-- Just ignore this one - avoids parsing warnings -->
+      </xsl:when>
+
+      <xsl:otherwise>
+        <xsl:message terminate="yes">
+          <xsl:text>Invalid 'table' parameter value: </xsl:text><xsl:value-of select="$table"/>
+        </xsl:message>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+
+
+
+
+  <!-- TABLE: systems - all rteval versions -->
+  <xsl:template match="/rteval['2.0' > @version]/HardwareInfo|/rteval[@version >= '2.0']/SystemInfo/DMIinfo/HardwareInfo" mode="tbl_systems">
+    <sqldata schemaver="1.0" table="systems" key="syskey">
+      <fields>
+        <field fid="0">sysid</field>
+        <field fid="1">dmidata</field>
+      </fields>
+      <records>
+        <record>
+          <value fid="0" hash="sha1">
+            <xsl:value-of select="concat(@SystemUUID,':',@SerialNo)"/>
+          </value>
+          <value fid="1" type="xmlblob">
+            <xsl:copy-of select="."/>
+          </value>
+        </record>
+      </records>
+    </sqldata>
+  </xsl:template>
+
+
+
+  <!-- TABLE: systems_hostname - rteval v1.37 or older -->
+  <xsl:template match="/rteval['2.0' > @version]" mode="tbl_systems_hostname">
+    <xsl:if test="string(number($syskey)) = 'NaN'">
+      <xsl:message terminate="yes">
+        <xsl:text>Invalid 'syskey' parameter value: </xsl:text><xsl:value-of select="syskey"/>
+      </xsl:message>
+    </xsl:if>
+    <sqldata schemaver="1.0" table="systems_hostname">
+      <xsl:call-template name="fields_systems_hostname"/>
+      <records>
+        <record>
+          <value fid="0"><xsl:value-of select="$syskey"/></value>
+          <value fid="1"><xsl:value-of select="uname/node"/></value>
+          <value fid="2"><xsl:value-of select="network_config/interface/IPv4[@defaultgw=1]/@ipaddr"/></value>
+        </record>
+      </records>
+    </sqldata>
+  </xsl:template>
+
+  <!-- TABLE: systems_hostname - rteval v2.0 or newer -->
+  <xsl:template match="/rteval[@version >= '2.0']/SystemInfo" mode="tbl_systems_hostname">
+    <xsl:if test="string(number($syskey)) = 'NaN'">
+      <xsl:message terminate="yes">
+        <xsl:text>Invalid 'syskey' parameter value: </xsl:text><xsl:value-of select="syskey"/>
+      </xsl:message>
+    </xsl:if>
+    <sqldata schemaver="1.0" table="systems_hostname">
+      <xsl:call-template name="fields_systems_hostname"/>
+      <records>
+        <record>
+          <value fid="0"><xsl:value-of select="$syskey"/></value>
+          <value fid="1"><xsl:value-of select="uname/node"/></value>
+          <value fid="2"><xsl:value-of select="NetworkConfig/interface/IPv4[@defaultgw=1]/@ipaddr"/></value>
+        </record>
+      </records>
+    </sqldata>
+  </xsl:template>
+
+  <xsl:template name="fields_systems_hostname">
+    <fields>
+      <field fid="0">syskey</field>
+      <field fid="1">hostname</field>
+      <field fid="2">ipaddr</field>
+    </fields>
+  </xsl:template>
+
+
+
+  <!-- TABLE: rtevalruns - rteval v1.37 or older -->
+  <xsl:template match="/rteval['2.0' > @version]" mode="tbl_rtevalruns">
+    <sqldata schemaver="1.2" table="rtevalruns">
+      <xsl:call-template name="fields_rtevalruns"/>
+      <records>
+        <record>
+          <value fid="0"><xsl:value-of select="$syskey"/></value>
+          <value fid="1"><xsl:value-of select="uname/kernel"/></value>
+          <value fid="2"><xsl:choose>
+            <xsl:when test="uname/kernel/@is_RT = '1'">true</xsl:when>
+            <xsl:otherwise>false</xsl:otherwise></xsl:choose>
+          </value>
+          <value fid="3"><xsl:value-of select="uname/arch"/></value>
+          <value fid="4"><xsl:value-of select="concat(run_info/date, ' ', run_info/time)"/></value>
+          <value fid="5">
+            <xsl:value-of select="(run_info/@days*86400)+(run_info/@hours*3600)
+                                  +(run_info/@minutes*60)+(run_info/@seconds)"/>
+          </value>
+          <value fid="6"><xsl:value-of select="loads/@load_average"/></value>
+          <value fid="7"><xsl:value-of select="@version"/></value>
+          <value fid="8"><xsl:value-of select="$report_filename"/></value>
+          <value fid="9"><xsl:value-of select="$rterid"/></value>
+          <value fid="10"><xsl:value-of select="$submid"/></value>
+          <value fid="11"><xsl:value-of select="uname/baseos"/></value>
+        </record>
+      </records>
+    </sqldata>
+  </xsl:template>
+
+  <!-- TABLE: rtevalruns - rteval v2.0 or newer -->
+  <xsl:template match="/rteval[@version >= '2.0']" mode="tbl_rtevalruns">
+    <sqldata schemaver="1.2" table="rtevalruns">
+      <xsl:call-template name="fields_rtevalruns"/>
+      <records>
+        <record>
+          <value fid="0"><xsl:value-of select="$syskey"/></value>
+          <value fid="1"><xsl:value-of select="SystemInfo/uname/kernel"/></value>
+          <value fid="2"><xsl:choose>
+            <xsl:when test="SystemInfo/uname/kernel/@is_RT = '1'">true</xsl:when>
+            <xsl:otherwise>false</xsl:otherwise></xsl:choose>
+          </value>
+          <value fid="3"><xsl:value-of select="SystemInfo/uname/arch"/></value>
+          <value fid="4"><xsl:value-of select="concat(run_info/date, ' ', run_info/time)"/></value>
+          <value fid="5">
+            <xsl:value-of select="(run_info/@days*86400)+(run_info/@hours*3600)
+                                  +(run_info/@minutes*60)+(run_info/@seconds)"/>
+          </value>
+          <value fid="6"><xsl:value-of select="loads/@load_average"/></value>
+          <value fid="7"><xsl:value-of select="@version"/></value>
+          <value fid="8"><xsl:value-of select="$report_filename"/></value>
+          <value fid="9"><xsl:value-of select="$rterid"/></value>
+          <value fid="10"><xsl:value-of select="$submid"/></value>
+          <value fid="11"><xsl:value-of select="SystemInfo/uname/baseos"/></value>
+        </record>
+      </records>
+    </sqldata>
+  </xsl:template>
+
+  <xsl:template name="fields_rtevalruns">
+    <xsl:if test="string(number($syskey)) = 'NaN'">
+      <xsl:message terminate="yes">
+        <xsl:text>Invalid 'syskey' parameter value: </xsl:text><xsl:value-of select="syskey"/>
+      </xsl:message>
+    </xsl:if>
+    <xsl:if test="string(number($rterid)) = 'NaN'">
+      <xsl:message terminate="yes">
+        <xsl:text>Invalid 'rterid' parameter value: </xsl:text><xsl:value-of select="$rterid"/>
+      </xsl:message>
+    </xsl:if>
+    <xsl:if test="string(number($submid)) = 'NaN'">
+      <xsl:message terminate="yes">
+        <xsl:text>Invalid 'submid' parameter value: </xsl:text><xsl:value-of select="$rterid"/>
+      </xsl:message>
+    </xsl:if>
+    <xsl:if test="$report_filename = ''">
+      <xsl:message terminate="yes">
+        <xsl:text>The parameter 'report_filename' parameter cannot be empty</xsl:text>
+      </xsl:message>
+    </xsl:if>
+      <fields>
+        <field fid="0">syskey</field>
+        <field fid="1">kernel_ver</field>
+        <field fid="2">kernel_rt</field>
+        <field fid="3">arch</field>
+        <field fid="4">run_start</field>
+        <field fid="5">run_duration</field>
+        <field fid="6">load_avg</field>
+        <field fid="7">version</field>
+        <field fid="8">report_filename</field>
+        <field fid="9">rterid</field>
+        <field fid="10">submid</field>
+        <field fid="11">distro</field>
+      </fields>
+  </xsl:template>
+
+
+
+  <!-- TABLE: rtevalruns_details - rteval v1.37 or older -->
+  <xsl:template match="/rteval['2.0' > @version]" mode="tbl_rtevalruns_details">
+    <sqldata schemaver="1.4" table="rtevalruns_details">
+      <xsl:call-template name="fields_rtevalruns_details"/>
+      <records>
+        <record>
+          <value fid="0"><xsl:value-of select="$rterid"/></value>
+          <value fid="1"><xsl:value-of select="hardware/numa_nodes"/></value>
+          <value fid="2">
+            <xsl:choose>
+              <xsl:when test="hardware/cpu_topology">
+                <xsl:value-of select="hardware/cpu_topology/@num_cpu_cores"/>
+              </xsl:when>
+              <xsl:otherwise>
+                <xsl:value-of select="hardware/cpu_cores"/>
+              </xsl:otherwise>
+            </xsl:choose>
+          </value>
+          <value fid="3">
+            <xsl:value-of select="hardware/cpu_topology/@num_cpu_sockets"/>
+          </value>
+          <value fid="4" type="xmlblob">
+            <rteval_details>
+              <xsl:copy-of select="clocksource|services|kthreads|network_config|loads|cyclictest/command_line"/>
+              <hardware>
+                <xsl:copy-of select="hardware/memory_size|hardware/cpu_topology"/>
+              </hardware>
+            </rteval_details>
+          </value>
+          <value fid="5"><xsl:value-of select="run_info/annotate"/></value>
+          <value fid="6" type="array">
+            <xsl:for-each select="hardware/cpu_topology/cpu[generate-id() = generate-id(key('pkgkey', @physical_package_id)[1])]">
+              <xsl:call-template name="count_core_spread_v1x">
+                <xsl:with-param name="pkgid" select="@physical_package_id"/>
+              </xsl:call-template>
+            </xsl:for-each>
+          </value>
+        </record>
+      </records>
+    </sqldata>
+  </xsl:template>
+
+  <!-- TABLE: rtevalruns_details - rteval v2.0 or newer -->
+  <xsl:template match="/rteval[@version >= '2.0']/SystemInfo" mode="tbl_rtevalruns_details">
+    <sqldata schemaver="1.4" table="rtevalruns_details">
+      <xsl:call-template name="fields_rtevalruns_details"/>
+      <records>
+        <record>
+          <value fid="0"><xsl:value-of select="$rterid"/></value>
+          <value fid="1"><xsl:value-of select="Memory/numa_nodes"/></value>
+          <value fid="2">
+            <xsl:value-of select="CPUtopology/@num_cpu_cores"/>
+          </value>
+          <value fid="3">
+            <xsl:value-of select="CPUtopology/@num_cpu_sockets"/>
+          </value>
+          <value fid="4" type="xmlblob">
+            <rteval_details format="2">
+              <xsl:copy-of select="Kernel/ClockSource|Services|Kernel/kthreads|NetworkConfig"/>
+              <xsl:copy-of select="../loads"/>
+              <Measurement>
+                <xsl:for-each select="../Measurements/Profile/*">
+                  <module>
+                    <xsl:attribute name="name"><xsl:value-of select="name(.)"/></xsl:attribute>
+                    <xsl:if test="@command_line">
+                      <command_line><xsl:value-of select="@command_line"/></command_line>
+                    </xsl:if>
+                    <xsl:copy-of select="timestamps"/>
+                  </module>
+                </xsl:for-each>
+              </Measurement>
+              <hardware>
+                <xsl:copy-of select="Memory|CPUtopology"/>
+              </hardware>
+            </rteval_details>
+          </value>
+          <value fid="5"><xsl:value-of select="../run_info/annotate"/></value>
+          <value fid="6" type="array">
+            <xsl:for-each select="CPUtopology/cpu[generate-id() = generate-id(key('pkgkey', @physical_package_id)[1])]">
+              <xsl:call-template name="count_core_spread_v2x">
+                <xsl:with-param name="pkgid" select="@physical_package_id"/>
+              </xsl:call-template>
+            </xsl:for-each>
+          </value>
+        </record>
+      </records>
+    </sqldata>
+  </xsl:template>
+
+  <xsl:template name="fields_rtevalruns_details">
+    <xsl:if test="string(number($rterid)) = 'NaN'">
+      <xsl:message terminate="yes">
+        <xsl:text>Invalid 'rterid' parameter value: </xsl:text><xsl:value-of select="$rterid"/>
+      </xsl:message>
+    </xsl:if>
+    <fields>
+      <field fid="0">rterid</field>
+      <field fid="1">numa_nodes</field>
+      <field fid="2">num_cpu_cores</field>
+      <field fid="3">num_cpu_sockets</field>
+      <field fid="4">xmldata</field>
+      <field fid="5">annotation</field>
+      <field fid="6">cpu_core_spread</field>
+    </fields>
+  </xsl:template>
+
+
+
+  <!-- TABLE: cyclic_statistics - rteval - all version -->
+  <xsl:template match="/rteval['2.0' > @version]/cyclictest|/rteval[@version >= '2.0']/Measurements/Profile/cyclictest"
+                mode="tbl_cyclictest_statistics">
+    <xsl:if test="string(number($rterid)) = 'NaN'">
+      <xsl:message terminate="yes">
+        <xsl:text>Invalid 'rterid' parameter value: </xsl:text><xsl:value-of select="$rterid"/>
+      </xsl:message>
+    </xsl:if>
+    <sqldata schemaver="1.1" table="cyclic_statistics">
+      <fields>
+        <field fid="0">rterid</field>
+        <field fid="1">coreid</field>
+        <field fid="2">priority</field>
+        <field fid="3">num_samples</field>
+        <field fid="4">lat_min</field>
+        <field fid="5">lat_max</field>
+        <field fid="6">lat_mean</field>
+        <field fid="7">mode</field>
+        <field fid="8">range</field>
+        <field fid="9">median</field>
+        <field fid="10">stddev</field>
+        <field fid="11">mean_abs_dev</field>
+      </fields>
+      <records>
+        <xsl:for-each select="core/statistics[samples > 0]|system/statistics[samples > 0]">
+          <record>
+            <value fid="0"><xsl:value-of select="$rterid"/></value>
+            <value fid="1"><xsl:choose>
+              <xsl:when test="../@id"><xsl:value-of select="../@id"/></xsl:when>
+              <xsl:otherwise><xsl:attribute name="isnull">1</xsl:attribute></xsl:otherwise></xsl:choose>
+            </value>
+            <value fid="2"><xsl:choose>
+              <xsl:when test="../@priority"><xsl:value-of select="../@priority"/></xsl:when>
+              <xsl:otherwise><xsl:attribute name="isnull">1</xsl:attribute></xsl:otherwise></xsl:choose>
+            </value>
+            <value fid="3"><xsl:value-of select="samples"/></value>
+            <value fid="4"><xsl:value-of select="minimum"/></value>
+            <value fid="5"><xsl:value-of select="maximum"/></value>
+            <value fid="6"><xsl:value-of select="mean"/></value>
+            <value fid="7"><xsl:value-of select="mode"/></value>
+            <value fid="8"><xsl:value-of select="range"/></value>
+            <value fid="9"><xsl:value-of select="median"/></value>
+            <value fid="10"><xsl:value-of select="standard_deviation"/></value>
+            <value fid="11"><xsl:value-of select="mean_absolute_deviation"/></value>
+          </record>
+        </xsl:for-each>
+      </records>
+    </sqldata>
+  </xsl:template>
+
+
+
+  <!-- TABLE: cyclic_histogram - rteval - all version -->
+  <xsl:template match="/rteval['2.0' > @version]/cyclictest|/rteval[@version >= '2.0']/Measurements/Profile/cyclictest"
+                mode="tbl_cyclictest_histogram">
+    <xsl:if test="string(number($rterid)) = 'NaN'">
+      <xsl:message terminate="yes">
+        <xsl:text>Invalid 'rterid' parameter value: </xsl:text><xsl:value-of select="$rterid"/>
+      </xsl:message>
+    </xsl:if>
+    <sqldata schemaver="1.0" table="cyclic_histogram">
+      <fields>
+        <field fid="0">rterid</field>
+        <field fid="1">core</field>
+        <field fid="2">index</field>
+        <field fid="3">value</field>
+      </fields>
+      <records>
+        <!-- Do it in this order, so the overall system results are parsed first -->
+        <xsl:apply-templates select="./system/histogram/bucket" mode="cyclic_histogram_rec_sql"/>
+        <xsl:apply-templates select="./core/histogram/bucket" mode="cyclic_histogram_rec_sql"/>
+      </records>
+    </sqldata>
+  </xsl:template>
+
+  <xsl:template match="/rteval['2.0' > @version]/cyclictest/*/histogram/bucket|/rteval[@version >= '2.0']/Measurements/Profile/cyclictest/*/histogram/bucket"
 		mode="cyclic_histogram_rec_sql">
       <record>
 	<value fid="0"><xsl:value-of select="$rterid"/></value>
@@ -302,11 +523,98 @@
       </record>
   </xsl:template>
 
-  <!-- Helper "function" for generating a core per physical socket spread overview -->
-  <xsl:template name="count_core_spread">
+
+
+  <!-- TABLE: hwlatdetect_summar - only for rteval 2.0 and newer -->
+  <xsl:template match="/rteval[@version >= '2.0']/Measurements/Profile/hwlatdetect" mode="tbl_hwlatdetect_summary">
+    <xsl:if test="string(number($rterid)) = 'NaN'">
+      <xsl:message terminate="yes">
+        <xsl:text>Invalid 'rterid' parameter value: </xsl:text><xsl:value-of select="$rterid"/>
+      </xsl:message>
+    </xsl:if>
+    <sqldata schemaver="1.5" table="hwlatdetect_summary">
+      <fields>
+        <field fid="0">rterid</field>
+        <field fid="1">duration</field>
+        <field fid="2">threshold</field>
+        <field fid="3">timewindow</field>
+        <field fid="4">width</field>
+        <field fid="5">samplecount</field>
+        <field fid="6">hwlat_min</field>
+        <field fid="7">hwlat_avg</field>
+        <field fid="8">hwlat_max</field>
+      </fields>
+      <records>
+        <record>
+          <value fid="0"><xsl:value-of select="$rterid"/></value>
+          <value fid="1"><xsl:value-of select="RunParams/@duration"/></value>
+          <value fid="2"><xsl:value-of select="RunParams/@threshold"/></value>
+          <value fid="3"><xsl:value-of select="RunParams/@window"/></value>
+          <value fid="4"><xsl:value-of select="RunParams/@width"/></value>
+          <value fid="5"><xsl:value-of select="samples/@count"/></value>
+          <xsl:choose>
+            <xsl:when test="samples/@count > 0">
+              <value fid="6"><xsl:value-of select="math:min(samples/sample/@duration)"/></value>
+              <value fid="7"><xsl:value-of select="sum(samples/sample/@duration) div samples/@count"/></value>
+              <value fid="8"><xsl:value-of select="math:max(samples/sample/@duration)"/></value>
+            </xsl:when>
+            <xsl:otherwise>
+              <value fid="6">0</value>
+              <value fid="7">0</value>
+              <value fid="8">0</value>
+            </xsl:otherwise>
+          </xsl:choose>
+        </record>
+      </records>
+    </sqldata>
+  </xsl:template>
+
+
+
+  <!-- TABLE: hwlatdetect_samples - only for rteval 2.0 and newer -->
+  <xsl:template match="/rteval[@version >='2.0']/Measurements/Profile/hwlatdetect/samples" mode="tbl_hwlatdetect_samples">
+    <xsl:if test="string(number($rterid)) = 'NaN'">
+      <xsl:message terminate="yes">
+        <xsl:text>Invalid 'rterid' parameter value: </xsl:text><xsl:value-of select="$rterid"/>
+      </xsl:message>
+    </xsl:if>
+    <sqldata schemaver="1.5" table="hwlatdetect_samples">
+      <fields>
+        <field fid="0">rterid</field>
+        <field fid="1">timestamp</field>
+        <field fid="2">latency</field>
+      </fields>
+      <records>
+        <xsl:apply-templates select="sample" mode="hwlatdetect_samples"/>
+      </records>
+    </sqldata>
+  </xsl:template>
+
+  <xsl:template match="/rteval[@version >='2.0']/Measurements/Profile/hwlatdetect[@format='1.0']/samples/sample" mode="hwlatdetect_samples">
+    <record>
+      <value fid="0"><xsl:value-of select="$rterid"/></value>
+      <value fid="1"><xsl:value-of select="@timestamp"/></value>
+      <value fid="2"><xsl:value-of select="@duration"/></value>
+    </record>
+  </xsl:template>
+
+
+
+
+  <!-- Helper "function" for generating a core per physical socket spread overview - for rteval v1.37 or older -->
+  <xsl:template name="count_core_spread_v1x">
     <xsl:param name="pkgid"/>
     <value>
       <xsl:value-of select="count(/rteval/hardware/cpu_topology/cpu[@physical_package_id = $pkgid])"/>
     </value>
   </xsl:template>
+
+  <!-- Helper "function" for generating a core per physical socket spread overview - for rteval v2.0 or newer -->
+  <xsl:template name="count_core_spread_v2x">
+    <xsl:param name="pkgid"/>
+    <value>
+      <xsl:value-of select="count(/rteval/SystemInfo/CPUtopology/cpu[@physical_package_id = $pkgid])"/>
+    </value>
+  </xsl:template>
+
 </xsl:stylesheet>
diff --git a/server/remove_rtevalrun b/server/remove_rtevalrun
index d1251d3..db89f9c 100755
--- a/server/remove_rtevalrun
+++ b/server/remove_rtevalrun
@@ -4,7 +4,7 @@
 #   A script intended to be run on the database server, which
 #   removes a given rteval run, based on the 'rterid' value
 #
-#   Copyright 2009      David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -16,9 +16,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/server/rteval-parser.spec b/server/rteval-parser.spec
index a348b01..00c75bf 100644
--- a/server/rteval-parser.spec
+++ b/server/rteval-parser.spec
@@ -1,6 +1,6 @@
 Name:		rteval-parser
-Version:	1.5
-%define sqlschemaver 1.4
+Version:	1.6
+%define sqlschemaver 1.5
 Release:	1%{?dist}
 Summary:	Report parser daemon for  rteval XML-RPC
 %define pkgname rteval-xmlrpc-%{version}
@@ -87,6 +87,10 @@
 
 
 %changelog
+* Thu Nov 15 2012 David Sommerseth <davids@redhat.com> - 1.6-1
+- Make rteval-parserd have no hard coded measurement data table restrictions
+- Added support for hwlatdetect data
+
 * Fri Oct  7 2011 David Sommerseth <dazo@users.sourceforge.net> - 1.5-1
 - Added support for storing data as arrays in PostgreSQL
 - Updated SQL schema to store CPU topology/core spread as an array in the database
diff --git a/server/rteval_testserver.py b/server/rteval_testserver.py
index 49bbfd7..cb58e34 100644
--- a/server/rteval_testserver.py
+++ b/server/rteval_testserver.py
@@ -2,7 +2,7 @@
 #   rteval_testserver.py
 #   Local XML-RPC test server.  Can be used to verify XML-RPC behavoiur
 #
-#   Copyright 2009      David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -14,9 +14,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/server/rteval_xmlrpc.py b/server/rteval_xmlrpc.py
index 4362d89..38cf9bc 100644
--- a/server/rteval_xmlrpc.py
+++ b/server/rteval_xmlrpc.py
@@ -2,7 +2,7 @@
 #   rteval_xmlrpc.py
 #   XML-RPC handler for mod_python which will receive requests
 #
-#   Copyright 2009      David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -14,9 +14,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/server/rteval_xmlrpc.wsgi b/server/rteval_xmlrpc.wsgi
index f782657..37f1665 100644
--- a/server/rteval_xmlrpc.wsgi
+++ b/server/rteval_xmlrpc.wsgi
@@ -2,7 +2,7 @@
 #   rteval_xmlrpc.wsgi
 #   XML-RPC handler for the rteval server, using mod_wsgi
 #
-#   Copyright 2011      David Sommerseth <davids@redhat.com>
+#   Copyright 2011 - 2013   David Sommerseth <davids@redhat.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
@@ -14,9 +14,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/server/rtevaldb.py b/server/rtevaldb.py
index e778358..c2478a3 100644
--- a/server/rtevaldb.py
+++ b/server/rtevaldb.py
@@ -2,7 +2,7 @@
 #   rtevaldb.py
 #   Function for registering a rteval summary.xml report into the database
 #
-#   Copyright 2009      David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -14,9 +14,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/server/sql/delta-1.4_1.5.sql b/server/sql/delta-1.4_1.5.sql
new file mode 100644
index 0000000..a190326
--- /dev/null
+++ b/server/sql/delta-1.4_1.5.sql
@@ -0,0 +1,30 @@
+-- SQL delta update from rteval-1.4.sql to rteval-1.5.sql
+
+UPDATE rteval_info SET value = '1.5' WHERE key = 'sql_schema_ver';
+
+-- TABLE: hwlatdetect_summary
+-- Tracks hwlatdetect results for a particular hardware
+--
+   CREATE TABLE hwlatdetect_summary (
+       rterid         INTEGER REFERENCES rtevalruns(rterid) NOT NULL,
+       duration       INTEGER NOT NULL,
+       threshold      INTEGER NOT NULL,
+       timewindow     INTEGER NOT NULL,
+       width          INTEGER NOT NULL,
+       samplecount    INTEGER NOT NULL,
+       hwlat_min      REAL NOT NULL,
+       hwlat_avg      REAL NOT NULL,
+       hwlat_max      REAL NOT NULL
+   ) WITHOUT OIDS;
+   GRANT SELECT, INSERT ON hwlatdetect_summary TO rtevparser;
+
+-- TABLE: hwlatdetect_samples
+-- Contains the hwlatdetect sample records from a particular run
+--
+   CREATE TABLE hwlatdetect_samples (
+       rterid         INTEGER REFERENCES rtevalruns(rterid) NOT NULL,
+       timestamp      NUMERIC(20,10) NOT NULL,
+       latency        REAL NOT NULL
+   ) WITHOUT OIDS;
+   GRANT SELECT, INSERT ON hwlatdetect_samples TO rtevparser;
+
diff --git a/server/sql/rteval-1.0.sql b/server/sql/rteval-1.0.sql
index 3b9ed3e..2ff3aae 100644
--- a/server/sql/rteval-1.0.sql
+++ b/server/sql/rteval-1.0.sql
@@ -103,6 +103,7 @@
     ) WITH OIDS;
 
     GRANT SELECT,INSERT ON rtevalruns TO rtevparser;
+    GRANT SELECT ON rtevalruns TO rtevxmlrpc;
     GRANT USAGE ON rtevalruns_rterid_seq TO rtevparser;
 
 -- TABLE rtevalruns_details
diff --git a/server/sql/rteval-1.1.sql b/server/sql/rteval-1.1.sql
index bd249fa..3977568 100644
--- a/server/sql/rteval-1.1.sql
+++ b/server/sql/rteval-1.1.sql
@@ -115,6 +115,7 @@
     ) WITH OIDS;
 
     GRANT SELECT,INSERT ON rtevalruns TO rtevparser;
+    GRANT SELECT ON rtevalruns TO rtevxmlrpc;
     GRANT USAGE ON rtevalruns_rterid_seq TO rtevparser;
 
 -- TABLE rtevalruns_details
diff --git a/server/sql/rteval-1.2.sql b/server/sql/rteval-1.2.sql
index 6512364..05bf33b 100644
--- a/server/sql/rteval-1.2.sql
+++ b/server/sql/rteval-1.2.sql
@@ -116,6 +116,7 @@
     ) WITH OIDS;
 
     GRANT SELECT,INSERT ON rtevalruns TO rtevparser;
+    GRANT SELECT ON rtevalruns TO rtevxmlrpc;
     GRANT USAGE ON rtevalruns_rterid_seq TO rtevparser;
 
 -- TABLE rtevalruns_details
diff --git a/server/sql/rteval-1.3.sql b/server/sql/rteval-1.3.sql
index 0723cc1..4082ab4 100644
--- a/server/sql/rteval-1.3.sql
+++ b/server/sql/rteval-1.3.sql
@@ -116,6 +116,7 @@
     ) WITH OIDS;
 
     GRANT SELECT,INSERT ON rtevalruns TO rtevparser;
+    GRANT SELECT ON rtevalruns TO rtevxmlrpc;
     GRANT USAGE ON rtevalruns_rterid_seq TO rtevparser;
 
 -- TABLE rtevalruns_details
diff --git a/server/sql/rteval-1.4.sql b/server/sql/rteval-1.4.sql
index 048600c..d6262af 100644
--- a/server/sql/rteval-1.4.sql
+++ b/server/sql/rteval-1.4.sql
@@ -116,6 +116,7 @@
     ) WITH OIDS;
 
     GRANT SELECT,INSERT ON rtevalruns TO rtevparser;
+    GRANT SELECT ON rtevalruns TO rtevxmlrpc;
     GRANT USAGE ON rtevalruns_rterid_seq TO rtevparser;
 
 -- TABLE rtevalruns_details
diff --git a/server/sql/rteval-1.5.sql b/server/sql/rteval-1.5.sql
new file mode 100644
index 0000000..c73dd0d
--- /dev/null
+++ b/server/sql/rteval-1.5.sql
@@ -0,0 +1,234 @@
+-- Create rteval database users
+--
+CREATE USER rtevxmlrpc NOSUPERUSER ENCRYPTED PASSWORD 'rtevaldb';
+CREATE USER rtevparser NOSUPERUSER ENCRYPTED PASSWORD 'rtevaldb_parser';
+
+-- Create rteval database
+--
+CREATE DATABASE rteval ENCODING 'utf-8';
+
+\c rteval
+
+-- TABLE: rteval_info
+-- Contains information the current rteval XML-RPC and parser installation
+--
+    CREATE TABLE rteval_info (
+       key    varchar(32) NOT NULL,
+       value  TEXT NOT NULL,
+       rtiid  SERIAL,
+       PRIMARY KEY(rtiid)
+    );
+    GRANT SELECT ON rteval_info TO rtevparser;
+    INSERT INTO rteval_info (key, value) VALUES ('sql_schema_ver','1.5');
+
+-- Enable plpgsql.  It is expected that this PL/pgSQL is available.
+    CREATE LANGUAGE 'plpgsql';
+
+-- FUNCTION: trgfnc_submqueue_notify
+-- Trigger function which is called on INSERT queries to the submissionqueue table.
+-- It will send a NOTIFY rteval_submq on INSERTs.
+--
+    CREATE FUNCTION trgfnc_submqueue_notify() RETURNS TRIGGER
+    AS $BODY$
+      DECLARE
+      BEGIN
+        NOTIFY rteval_submq;
+        RETURN NEW;
+      END
+    $BODY$ LANGUAGE 'plpgsql';
+
+    -- The user(s) which are allowed to do INSERT on the submissionqueue
+    -- must also be allowed to call this trigger function.
+    GRANT EXECUTE ON FUNCTION trgfnc_submqueue_notify() TO rtevxmlrpc;
+
+-- TABLE: submissionqueue
+-- All XML-RPC clients registers their submissions into this table.  Another parser thread
+-- will pickup the records where parsestart IS NULL.
+--
+    CREATE TABLE submissionqueue (
+           clientid   varchar(128) NOT NULL,
+           filename   VARCHAR(1024) NOT NULL,
+           status     INTEGER DEFAULT '0',
+           received   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+           parsestart TIMESTAMP WITH TIME ZONE,
+           parseend   TIMESTAMP WITH TIME ZONE,
+           submid     SERIAL,
+           PRIMARY KEY(submid)
+    ) WITH OIDS;
+    CREATE INDEX submissionq_status ON submissionqueue(status);
+
+    CREATE TRIGGER trg_submissionqueue AFTER INSERT
+           ON submissionqueue FOR EACH STATEMENT
+	   EXECUTE PROCEDURE trgfnc_submqueue_notify();
+
+    GRANT SELECT, INSERT ON submissionqueue TO rtevxmlrpc;
+    GRANT USAGE ON submissionqueue_submid_seq TO rtevxmlrpc;
+    GRANT SELECT, UPDATE ON submissionqueue TO rtevparser;
+
+-- TABLE: systems
+-- Overview table over all systems which have sent reports
+-- The dmidata column will keep the complete DMIdata available
+-- for further information about the system.
+--
+    CREATE TABLE systems (
+        syskey        SERIAL NOT NULL,
+        sysid         VARCHAR(64) NOT NULL,
+        dmidata       xml NOT NULL,
+        PRIMARY KEY(syskey)
+    ) WITH OIDS;
+
+    GRANT SELECT,INSERT ON systems TO rtevparser;
+    GRANT USAGE ON systems_syskey_seq TO rtevparser;
+
+-- TABLE: systems_hostname
+-- This table is used to track the hostnames and IP addresses
+-- a registered system have used over time
+--
+   CREATE TABLE systems_hostname (
+        syskey        INTEGER REFERENCES systems(syskey) NOT NULL,
+        hostname      VARCHAR(256) NOT NULL,
+        ipaddr        cidr
+    ) WITH OIDS;
+    CREATE INDEX systems_hostname_syskey ON systems_hostname(syskey);
+    CREATE INDEX systems_hostname_hostname ON systems_hostname(hostname);
+    CREATE INDEX systems_hostname_ipaddr ON systems_hostname(ipaddr);
+
+    GRANT SELECT, INSERT ON systems_hostname TO rtevparser;
+
+
+-- TABLE: rtevalruns
+-- Overview over all rteval runs, when they were run and how long they ran.
+--
+    CREATE TABLE rtevalruns (
+        rterid          SERIAL NOT NULL, -- RTEval Run Id
+        submid          INTEGER REFERENCES submissionqueue(submid) NOT NULL,
+        syskey          INTEGER REFERENCES systems(syskey) NOT NULL,
+        kernel_ver      VARCHAR(32) NOT NULL,
+        kernel_rt       BOOLEAN NOT NULL,
+        arch            VARCHAR(12) NOT NULL,
+	distro		VARCHAR(64),
+        run_start       TIMESTAMP WITH TIME ZONE NOT NULL,
+        run_duration    INTEGER NOT NULL,
+        load_avg        REAL NOT NULL,
+        version         VARCHAR(4), -- Version of rteval
+        report_filename TEXT,
+        PRIMARY KEY(rterid)
+    ) WITH OIDS;
+
+    GRANT SELECT,INSERT ON rtevalruns TO rtevparser;
+    GRANT SELECT ON rtevalruns TO rtevxmlrpc;
+    GRANT USAGE ON rtevalruns_rterid_seq TO rtevparser;
+
+-- TABLE rtevalruns_details
+-- More specific information on the rteval run.  The data is stored
+-- in XML for flexibility
+--
+-- Tags being saved here includes: /rteval/clocksource, /rteval/hardware,
+-- /rteval/loads and /rteval/cyclictest/command_line
+--
+    CREATE TABLE rtevalruns_details (
+        rterid          INTEGER REFERENCES rtevalruns(rterid) NOT NULL,
+        annotation      TEXT,
+        num_cpu_cores   INTEGER,
+        num_cpu_sockets INTEGER,
+        cpu_core_spread INTEGER[],
+        numa_nodes      INTEGER,
+        xmldata         xml NOT NULL,
+        PRIMARY KEY(rterid)
+    );
+    GRANT INSERT ON rtevalruns_details TO rtevparser;
+
+-- TABLE: cyclic_statistics
+-- This table keeps statistics overview over a particular rteval run
+--
+    CREATE TABLE cyclic_statistics (
+        rterid        INTEGER REFERENCES rtevalruns(rterid) NOT NULL,
+        coreid        INTEGER, -- NULL=system
+        priority      INTEGER, -- NULL=system
+        num_samples   BIGINT NOT NULL,
+        lat_min       REAL NOT NULL,
+        lat_max       REAL NOT NULL,
+        lat_mean      REAL NOT NULL,
+        mode          REAL NOT NULL,
+        range         REAL NOT NULL,
+        median        REAL NOT NULL,
+        stddev        REAL NOT NULL,
+	mean_abs_dev  REAL NOT NULL,
+	variance      REAL NOT NULL,
+        cstid         SERIAL NOT NULL, -- unique record ID
+        PRIMARY KEY(cstid)
+    ) WITH OIDS;
+    CREATE INDEX cyclic_statistics_rterid ON cyclic_statistics(rterid);
+
+    GRANT INSERT ON cyclic_statistics TO rtevparser;
+    GRANT USAGE ON cyclic_statistics_cstid_seq TO rtevparser;
+
+-- TABLE: cyclic_histogram
+-- This table keeps the raw histogram data for each rteval run being
+-- reported.
+--
+    CREATE TABLE cyclic_histogram (
+        rterid        INTEGER REFERENCES rtevalruns(rterid) NOT NULL,
+        core          INTEGER, -- NULL=system
+        index         INTEGER NOT NULL,
+        value         BIGINT NOT NULL
+    ) WITHOUT OIDS;
+    CREATE INDEX cyclic_histogram_rterid ON cyclic_histogram(rterid);
+
+    GRANT INSERT ON cyclic_histogram TO rtevparser;
+
+-- TABLE: cyclic_rawdata
+-- This table keeps the raw data for each rteval run being reported.
+-- Due to that it will be an enormous amount of data, we avoid using
+-- OID on this table.
+--
+    CREATE TABLE cyclic_rawdata (
+        rterid        INTEGER REFERENCES rtevalruns(rterid) NOT NULL,
+        cpu_num       INTEGER NOT NULL,
+        sampleseq     INTEGER NOT NULL,
+        latency       REAL NOT NULL
+    ) WITHOUT OIDS;
+    CREATE INDEX cyclic_rawdata_rterid ON cyclic_rawdata(rterid);
+
+    GRANT INSERT ON cyclic_rawdata TO rtevparser;
+
+-- TABLE: hwlatdetect_summary
+-- Tracks hwlatdetect results for a particular hardware
+--
+   CREATE TABLE hwlatdetect_summary (
+       rterid         INTEGER REFERENCES rtevalruns(rterid) NOT NULL,
+       duration       INTEGER NOT NULL,
+       threshold      INTEGER NOT NULL,
+       timewindow     INTEGER NOT NULL,
+       width          INTEGER NOT NULL,
+       samplecount    INTEGER NOT NULL,
+       hwlat_min      REAL NOT NULL,
+       hwlat_avg      REAL NOT NULL,
+       hwlat_max      REAL NOT NULL
+   ) WITHOUT OIDS;
+   GRANT SELECT, INSERT ON hwlatdetect_summary TO rtevparser;
+
+-- TABLE: hwlatdetect_samples
+-- Contains the hwlatdetect sample records from a particular run
+--
+   CREATE TABLE hwlatdetect_samples (
+       rterid         INTEGER REFERENCES rtevalruns(rterid) NOT NULL,
+       timestamp      NUMERIC(20,10) NOT NULL,
+       latency        REAL NOT NULL
+   ) WITHOUT OIDS;
+   GRANT SELECT, INSERT ON hwlatdetect_samples TO rtevparser;
+
+-- TABLE: notes
+-- This table is purely to make notes, connected to different
+-- records in the database
+--
+    CREATE TABLE notes (
+        ntid          SERIAL NOT NULL,
+        reftbl        CHAR NOT NULL,    -- S=systems, R=rtevalruns
+        refid         INTEGER NOT NULL, -- reference id, to the corresponding table
+        notes         TEXT NOT NULL,
+        createdby     VARCHAR(48),
+        created       TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+        PRIMARY KEY(ntid)
+    ) WITH OIDS;
+    CREATE INDEX notes_refid ON notes(reftbl,refid);
diff --git a/server/testclient.py b/server/testclient.py
index 9632a7b..b00053b 100644
--- a/server/testclient.py
+++ b/server/testclient.py
@@ -3,7 +3,7 @@
 #   XML-RPC test client for testing the supported XML-RPC API 
 #   in the rteval server.
 #
-#   Copyright 2009      David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -15,9 +15,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/server/testclient_sendreportfile b/server/testclient_sendreportfile
index fe617ad..4def219 100755
--- a/server/testclient_sendreportfile
+++ b/server/testclient_sendreportfile
@@ -3,7 +3,7 @@
 #   testclient_sendfile.py
 #   XML-RPC test client which just sends an XML file to the given rteval server
 #
-#   Copyright 2009      David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -15,9 +15,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
diff --git a/server/xmlrpc_API1.py b/server/xmlrpc_API1.py
index 064f1b2..46cad14 100644
--- a/server/xmlrpc_API1.py
+++ b/server/xmlrpc_API1.py
@@ -2,7 +2,7 @@
 #   xmlrpc_API1.py
 #   XML-RPC functions supported by the API1 version for the rteval server
 #
-#   Copyright 2009      David Sommerseth <davids@redhat.com>
+#   Copyright 2009 - 2013   David Sommerseth <davids@redhat.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
@@ -14,9 +14,9 @@
 #   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
+#   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.,
+#   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 #   For the avoidance of doubt the "preferred form" of this code is one which
 #   is in an open unpatent encumbered format. Where cryptographic key signing
@@ -24,6 +24,7 @@
 #   including keys needed to generate an equivalently functional executable
 #   are deemed to be part of the source code.
 #
+
 import os
 import bz2
 import base64
@@ -93,7 +94,7 @@
 
         # Save a copy of the report on the file system
         # Make sure we have a directory to write files into
-        self.__mkdatadir(self.config.datadir + '/queue/')
+        self.__mkdatadir(os.path.join(self.config.datadir, 'queue'))
         fname = self.__getfilename('queue/', ('%s' % clientid), '.xml', False)
         xmldoc.saveFormatFileEnc(fname,'UTF-8',1)
         if self.debug:
diff --git a/setup.py b/setup.py
index f901307..f5dc146 100644
--- a/setup.py
+++ b/setup.py
@@ -2,17 +2,45 @@
 from distutils.sysconfig import get_python_lib
 from distutils.core import setup
 from os.path import isfile, join
-import glob
-import os
+import glob, os, shutil, gzip
+
 
 # Get PYTHONLIB with no prefix so --prefix installs work.
 PYTHONLIB = join(get_python_lib(standard_lib=1, prefix=''), 'site-packages')
 
+# Tiny hack to make rteval-cmd become a rteval when building/installing the package
+try:
+    os.mkdir('dist', 0755)
+    distcreated = True
+except OSError, e:
+    if e.errno == 17:
+        # If it already exists, ignore this error
+        distcreated = False
+    else:
+        raise e
+shutil.copy('rteval-cmd','dist/rteval')
+
+# Hack to avoid importing libxml2 and a lot of other stuff
+# when getting the rteval version.  These are modules which
+# might not be available on the build box.
+shutil.copy('rteval/version.py','dist/__init__.py')
+from dist import RTEVAL_VERSION
+
+# Compress the man page, so distutil will only care for the compressed file
+mangz = gzip.GzipFile('dist/rteval.8.gz', 'w', 9)
+man = open('doc/rteval.8', 'r')
+mangz.writelines(man)
+man.close()
+mangz.close()
+
+
+# Do the distutils stuff
 setup(name="rteval",
-      version = "1.41",
-      description = "evaluate system performance for Realtime",
-      author = "Clark Williams",
-      author_email = "williams@redhat.com",
+      version = RTEVAL_VERSION,
+      description = "Evaluate system performance for Realtime",
+      author = "Clark Williams, David Sommerseth",
+      author_email = "williams@redhat.com, davids@redhat.com",
+      url = "https://git.kernel.org/?p=linux/kernel/git/clrkwllms/rteval.git;a=summary",
       license = "GPLv2",
       long_description =
 """\
@@ -26,5 +54,36 @@
 analyzed for standard statistical measurements (i.e mode, median, range,
 mean, variance and standard deviation) and a report is generated.
 """,
-      packages = ["rteval"],
+      packages = ["rteval",
+                  "rteval.modules",
+                  "rteval.modules.loads",
+                  "rteval.modules.measurement",
+                  "rteval.sysinfo"],
+      package_dir = { "rteval": "rteval",
+                      "rteval.modules": "rteval/modules",
+                      "rteval.modules.loads": "rteval/modules/loads",
+                      "rteval.modules.measurement": "rteval/modules/measurement",
+                      "rteval.sysinfo": "rteval/sysinfo"
+                      },
+      data_files = [("share/rteval", ["rteval/rteval_dmi.xsl",
+                                      "rteval/rteval_histogram_raw.xsl",
+                                      "rteval/rteval_text.xsl"]),
+                    ("/etc", ["rteval.conf"]),
+                    ("share/man/man8", ["dist/rteval.8.gz"])
+                    ],
+      scripts = ["dist/rteval"]
       )
+
+
+# Clean-up from our little hack
+os.unlink('dist/rteval')
+os.unlink('dist/rteval.8.gz')
+os.unlink('dist/__init__.py')
+os.unlink('dist/__init__.pyc')
+
+if distcreated:
+    try:
+        os.rmdir('dist')
+    except OSError:
+        # Ignore any errors
+        pass