summaryrefslogtreecommitdiffstats
path: root/src/lib/bench
diff options
context:
space:
mode:
authorJINMEI Tatuya <jinmei@isc.org>2010-08-09 20:57:03 +0200
committerJINMEI Tatuya <jinmei@isc.org>2010-08-09 20:57:03 +0200
commit9a979bd5d08f8a11ecac4a6e12e2d287db4e972b (patch)
treeb5959646a265af01486a82b93b51fa740f6eea9a /src/lib/bench
parentadd revision to changelog (diff)
parentsync w/ trunk (diff)
downloadkea-9a979bd5d08f8a11ecac4a6e12e2d287db4e972b.tar.xz
kea-9a979bd5d08f8a11ecac4a6e12e2d287db4e972b.zip
merged branches/trac241, benchmark framework.
git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@2664 e5f2f494-b856-4b98-b285-d166d9295462
Diffstat (limited to 'src/lib/bench')
-rw-r--r--src/lib/bench/Makefile.am9
-rw-r--r--src/lib/bench/benchmark.h395
-rw-r--r--src/lib/bench/benchmark_util.cc116
-rw-r--r--src/lib/bench/benchmark_util.h149
-rw-r--r--src/lib/bench/example/Makefile.am9
-rw-r--r--src/lib/bench/example/search_bench.cc144
-rw-r--r--src/lib/bench/tests/Makefile.am22
-rw-r--r--src/lib/bench/tests/benchmark_unittest.cc143
-rw-r--r--src/lib/bench/tests/loadquery_unittest.cc198
-rw-r--r--src/lib/bench/tests/run_unittests.cc24
-rw-r--r--src/lib/bench/tests/testdata/query.txt6
11 files changed, 1215 insertions, 0 deletions
diff --git a/src/lib/bench/Makefile.am b/src/lib/bench/Makefile.am
new file mode 100644
index 0000000000..daa1b998fe
--- /dev/null
+++ b/src/lib/bench/Makefile.am
@@ -0,0 +1,9 @@
+SUBDIRS = . tests example
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libbench.la
+libbench_la_SOURCES = benchmark_util.h benchmark_util.cc
diff --git a/src/lib/bench/benchmark.h b/src/lib/bench/benchmark.h
new file mode 100644
index 0000000000..62c4cd390a
--- /dev/null
+++ b/src/lib/bench/benchmark.h
@@ -0,0 +1,395 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __BENCHMARK_H
+#define __BENCHMARK_H 1
+
+#include <sys/time.h>
+
+#include <iostream>
+#include <ios>
+
+namespace isc {
+namespace bench {
+
+/// \brief Templated micro benchmark framework.
+///
+/// "Premature optimization is the root of all evil."
+/// But programmers are often tempted to focus on performance optimization
+/// too early.
+/// Likewise, it's not uncommon for an engineer to introduce a minor
+/// optimization with a lot of complicated code logic that actually improves
+/// performance only marginally.
+/// Making benchmark easier will help avoid such common pitfalls.
+/// Of course, it also helps when we really need to introduce optimization
+/// to identify where is the bottleneck and see how a particular optimization
+/// improves the performance.
+///
+/// The BenchMark class template provides a simple framework for so-called
+/// "stopwatch" micro benchmarking.
+/// It just encapsulates the common operations for this type of benchmark:
+/// repeat a specified operation for a given number of times,
+/// record the start and end times of the operations,
+/// and provide major accumulated results such as the number of iterations
+/// per second.
+/// The main goal of this class is to help developers write benchmark test
+/// cases with fewer strokes of typing.
+///
+/// The constructors of a \c BenchMark class is to take the number of
+/// iterations (which is referred to as \c niter below)
+/// and an object (or reference to it) of the type of the template
+/// parameter, \c T. Class \c T implements the benchmark target code via
+/// its \c run() method, whose signature is as follows:
+/// \code unsigned int T::run(); \endcode
+///
+/// A BenchMark class object, via its own \c run() method, calls \c T::run()
+/// for \c niter times.
+/// In the simplest form \c T::run() would perform a single operation to
+/// be benchmarked and returns 1.
+/// In some cases, however, the operation is very lightweight (e.g. performing
+/// a binary search on a moderate length of integer vector), and it may be
+/// desirable to have an internal iterations within \c T::run() to avoid
+/// the overhead of function calls to \c T::run().
+/// In that case, \c T::run() would return the number of internal iterations
+/// instead of 1.
+///
+/// The \c BenchMark::run() method records some statistics %data on the
+/// benchmarking, including the start and end times and the total number of
+/// iterations (which is the sum of the return value of \c T::run(), and,
+/// is equal to \c niter in the simplest case where \c T::run() always
+/// returns 1).
+/// This %data can be retried via other methods of \c BenchMark, but in
+/// the primarily intended use case the \c BenchMark object would calls its
+/// \c run() method at the end of its construction, and prints summarized
+/// statistics to the standard output.
+/// This way, the developer can only write a single line of code to perform
+/// everything other than the benchmark target code (see the example below).
+///
+/// \b Example
+///
+/// Suppose that we want to measure performance of the search (find)
+/// operation on STL set objects. We'd first define the implementation
+/// class (to be the template parameter of the \c BenchMark class) as follows:
+///
+/// \code class SetSearchBenchMark {
+/// public:
+/// SetSearchBenchMark(const set<int>& data, const vector<int>& keys) :
+/// data_(data), keys_(keys)
+/// {}
+/// unsigned int run() {
+/// vector<int>::const_iterator iter;
+/// vector<int>::const_iterator end_key = keys_.end();
+/// for (iter = keys_.begin(); iter != end_key; ++iter) {
+/// data_.find(*iter);
+/// }
+/// return (keys_.size());
+/// }
+/// const set<int>& data_;
+/// const vector<int>& keys_;
+/// }; \endcode
+///
+/// In its constructor the \c SetSearchBenchMark class takes a set of
+/// integers (\c %data) and a vector of integers (\c keys). \c %data is
+/// the STL set to be searched, and \c keys stores the search keys.
+/// The constructor keeps local references to these objects.
+///
+/// The \c SetSearchBenchMark::run() method, which is called via
+/// \c BenchMark::run(), iterates over the key vector, and performs the
+/// \c find() method of the set class for each key.
+/// (This is only for performance measurement, so the result is ignored).
+/// Note that this \c run() method has its own internal iterations.
+/// This is because each invocation of \c find() would be pretty lightweight,
+/// and the function call overhead may be relatively heavier.
+/// Due to the internal iterations, this method returns the number of
+/// \c find() calls, which is equal to the size of the key vector.
+///
+/// Next, we should prepare test %data. In this simple example, let's assume
+/// we use a fixed size: a set of 10,000 integers (from 0 to 9999), and use
+/// the same number of search keys randomly chosen from that range:
+/// \code
+/// set<int> data_set;
+/// vector<int> keys;
+/// for (int i = 0; i < 10000; ++i) {
+/// data_set.insert(i);
+/// keys.push_back(rand() % 10000);
+/// } \endcode
+///
+/// Then construct a \c BenchMark<SetSearchBenchMark> object with the
+/// test %data:
+/// \code
+/// BenchMark<SetSearchBenchMark>(100, SetSearchBenchMark(data_set, keys));
+/// \endcode
+/// Here we specify 100 for the number of iterations, which would cause
+/// 1 million search attempts in total.
+///
+/// That's it. If we put these in a C++ source file with usual necessary
+/// stuff (such as \c %main()), compile it, and run the executable, then
+/// we'll see something like this:
+///
+/// \code Performed 1000000 iterations in 0.180172s (5550251.98ips) \endcode
+///
+/// It should be obvious what this means (ips stands for "iterations
+/// per second").
+///
+/// A complete example program of this measurement scenario (with some
+/// additional test cases and configurable parameters) can be found in
+/// example/search_bench.cc.
+///
+/// \b Customization
+///
+/// The above simple usage should be sufficient in many benchmark cases,
+/// but the \c BenchMark class provides some customization points by
+/// specializing some of its (templated) public methods.
+/// For example, assume you want to customize the output of benchmark result.
+/// It can be done by specializing \c BenchMark::printResult():
+/// \code namespace isc {
+/// namespace bench {
+/// template<>
+/// void
+/// BenchMark<SetSearchBenchMark>::printResult() const {
+/// cout << "Searched for " << target_.keys_.size() << " keys "
+/// << getIteration() << " times in " << getDuration() << "s" << endl;
+/// }
+/// }
+/// } \endcode
+///
+/// Then the Result would be something like this:
+///
+/// \code Searched for 10000 keys 1000000 times in 0.21s \endcode
+///
+/// Note that the specialization must be defined in the same namespace as
+/// that of the \c BenchMark class, that is, \c isc::bench.
+/// It should also be noted that the corresponding \c SetSearchBenchMark
+/// object can be accessed (through its public interfaces) via the \c target_
+/// member variable of \c BenchMark.
+///
+/// <b>Future Plans and Compatibility Notes</b>
+///
+/// Currently, benchmark developers need to write supplemental code that is
+/// not directly related to benchmarks (such as \c %main()) by hand.
+/// It would be better if we could minimize such development overhead.
+/// In future versions we may provide a common \c %main() function and
+/// option parsers, thereby allowing the developer to only write the benchmark
+/// classes and invoke them, just like what various unit test frameworks do.
+///
+/// If and when we implement it, some existing benchmark cases may need to be
+/// adjusted.
+template <typename T>
+class BenchMark {
+ ///
+ /// \name Constructors
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ /// We use the default destructor.
+ //@{
+private:
+ BenchMark(const BenchMark& source);
+ BenchMark& operator=(const BenchMark& source);
+public:
+ /// \bench Constructor for immediate run.
+ ///
+ /// This is the constructor that is expected to be used normally.
+ /// It runs the benchmark within the constructor and prints the result,
+ /// thereby making it possible to do everything with a single line of
+ /// code (see the above example).
+ ///
+ /// \param iterations The number of iterations. The \c run() method will
+ /// be called this number of times.
+ /// \param target The templated class object that
+ /// implements the code to be benchmarked.
+ BenchMark(const int iterations, T target) :
+ iterations_(iterations), sub_iterations_(0), target_(target)
+ {
+ initialize(true);
+ }
+
+ /// \bench Constructor for finer-grained control.
+ ///
+ /// This constructor takes the third parameter, \c immediate, to control
+ /// whether to run the benchmark within the constructor.
+ /// It also takes a reference to the templated class object rather than
+ /// an object copy, so if the copy overhead isn't acceptable this
+ /// constructor must be used.
+ ///
+ /// \param iterations The number of iterations. The \c run() method will
+ /// be called this number of times.
+ /// \param target A reference to the templated class object that
+ /// implements the code to be benchmarked.
+ /// \param immediate If \c true the benchmark will be performed within
+ /// the constructor; otherwise it only does initialization.
+ BenchMark(const int iterations, T& target, const bool immediate) :
+ iterations_(iterations), sub_iterations_(0), target_(target)
+ {
+ initialize(immediate);
+ }
+ //@}
+
+ /// \brief Hook to be called before starting benchmark.
+ ///
+ /// This method will be called from \c run() before starting the benchmark.
+ /// By default it's empty, but can be customized via template
+ /// specialization.
+ void setUp() {}
+
+ /// \brief Hook to be called after benchmark.
+ ///
+ /// This method will be called from \c run() when the benchmark completes.
+ /// By default it's empty, but can be customized via template
+ /// specialization.
+ void tearDown() {}
+
+ /// \brief Perform benchmark.
+ ///
+ /// This method first calls \c setUp().
+ /// It then records the current time, calls \c T::run() for the number
+ /// of times specified on construction, and records the time on completion.
+ /// Finally, it calls \c tearDown().
+ void run() {
+ setUp();
+
+ struct timeval beg, end;
+ gettimeofday(&beg, NULL);
+ for (int i = 0; i < iterations_; ++i) {
+ sub_iterations_ += target_.run();
+ }
+ gettimeofday(&end, NULL);
+ tv_diff_ = tv_subtract(end, beg);
+
+ tearDown();
+ }
+
+ /// \brief Print the benchmark result.
+ ///
+ /// This method prints the benchmark result in a common style to the
+ /// standard out. The result contains the number of total iterations,
+ /// the duration of the test, and the number of iterations per second
+ /// calculated from the previous two parameters.
+ ///
+ /// A call to this method is only meaningful after the completion of
+ /// \c run(). The behavior is undefined in other cases.
+ void printResult() const {
+ std::cout.precision(6);
+ std::cout << "Performed " << getIteration() << " iterations in "
+ << std::fixed << getDuration() << "s";
+ std::cout.precision(2);
+ std::cout << " (" << std::fixed << getIterationPerSecond() << "ips)"
+ << std::endl;
+ }
+
+ /// \brief Return the number of iterations.
+ ///
+ /// It returns the total iterations of benchmark, which is the sum
+ /// of the return value of \c T::run() over all calls to it
+ /// (note that it may not equal to the number of calls to \c T::run(),
+ /// which was specified on construction of this class).
+ ///
+ /// A call to this method is only meaningful after the completion of
+ /// \c run(). The behavior is undefined in other cases.
+ unsigned int getIteration() const { return (sub_iterations_); }
+
+ /// \brief Return the duration of benchmark in seconds.
+ ///
+ /// The highest possible precision of this value is microseconds.
+ ///
+ /// A call to this method is only meaningful after the completion of
+ /// \c run(). The behavior is undefined in other cases.
+ double getDuration() const {
+ return (tv_diff_.tv_sec +
+ static_cast<double>(tv_diff_.tv_usec) / ONE_MILLION);
+ }
+
+ /// \brief Return the average duration per iteration in seconds.
+ ///
+ /// The highest possible precision of this value is microseconds.
+ /// The iteration is the sum of the return value of \c T::run() over
+ /// all calls to it (note that it may not equal to the number of calls
+ /// to \c T::run()).
+ ///
+ /// If it cannot calculate the average, it returns \c TIME_FAILURE.
+ ///
+ /// A call to this method is only meaningful after the completion of
+ /// \c run(). The behavior is undefined in other cases.
+ double getAverageTime() const {
+ if (sub_iterations_ == 0) {
+ return (TIME_FAILURE);
+ }
+ return ((tv_diff_.tv_sec +
+ static_cast<double>(tv_diff_.tv_usec) / ONE_MILLION ) /
+ sub_iterations_);
+ }
+
+ /// \brief Return the number of possible iterations per second based on
+ /// the benchmark result.
+ ///
+ /// If it cannot calculate that number (e.g. because the duration is
+ /// too small) it returns \c ITERATION_FAILURE.
+ /// A call to this method is only meaningful after the completion of
+ /// \c run(). The behavior is undefined in other cases.
+ double getIterationPerSecond() const {
+ const double duration_usec = tv_diff_.tv_sec +
+ static_cast<double>(tv_diff_.tv_usec) / ONE_MILLION;
+ if (duration_usec == 0) {
+ return (ITERATION_FAILURE);
+ }
+ return (sub_iterations_ / duration_usec);
+ }
+public:
+ /// \brief A constant that indicates a failure in \c getAverageTime().
+ static const double TIME_FAILURE = -1;
+
+ /// \brief A constant that indicates a failure in
+ /// \c getIterationPerSecond().
+ static const double ITERATION_FAILURE = -1;
+private:
+ void initialize(const bool immediate) {
+ if (immediate) {
+ run();
+ printResult();
+ }
+ }
+private:
+ // return t1 - t2
+ struct timeval tv_subtract(const struct timeval& t1,
+ const struct timeval& t2)
+ {
+ struct timeval result;
+
+ result.tv_sec = t1.tv_sec - t2.tv_sec;
+ if (t1.tv_usec >= t2.tv_usec) {
+ result.tv_usec = t1.tv_usec- t2.tv_usec;
+ } else {
+ result.tv_usec = ONE_MILLION + t1.tv_usec - t2.tv_usec;
+ --result.tv_sec;
+ }
+
+ return (result);
+ }
+private:
+ static const int ONE_MILLION = 1000000;
+ const unsigned int iterations_;
+ unsigned int sub_iterations_;
+ T& target_;
+ struct timeval tv_diff_;
+};
+
+}
+}
+#endif // __BENCHMARK_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/bench/benchmark_util.cc b/src/lib/bench/benchmark_util.cc
new file mode 100644
index 0000000000..3705821d74
--- /dev/null
+++ b/src/lib/bench/benchmark_util.cc
@@ -0,0 +1,116 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/name.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrtype.h>
+#include <dns/rrclass.h>
+#include <dns/question.h>
+
+#include <bench/benchmark_util.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+
+namespace isc {
+namespace bench {
+void
+loadQueryData(const char* const input_file, BenchQueries& queries,
+ const RRClass& qclass, const bool strict)
+{
+ ifstream ifs;
+
+ ifs.open(input_file, ios_base::in);
+ if ((ifs.rdstate() & istream::failbit) != 0) {
+ isc_throw(BenchMarkError, "failed to load query data file: " +
+ string(input_file));
+ }
+ loadQueryData(ifs, queries, qclass, strict);
+ ifs.close();
+}
+
+void
+loadQueryData(istream& input, BenchQueries& queries, const RRClass& qclass,
+ const bool strict)
+{
+ string line;
+ unsigned int linenum = 0;
+ Message query_message(Message::RENDER);
+ OutputBuffer buffer(128); // this should be sufficiently large
+ MessageRenderer renderer(buffer);
+ while (getline(input, line), !input.eof()) {
+ ++linenum;
+ if (input.bad() || input.fail()) {
+ isc_throw(BenchMarkError,
+ "Unexpected line in query data file around line " <<
+ linenum);
+ }
+ if (line.empty() || line[0] == '#') {
+ continue; // skip comment and blank lines
+ }
+
+ istringstream iss(line);
+ string qname_string, qtype_string;
+ iss >> qname_string >> qtype_string;
+ if (iss.bad() || iss.fail()) {
+ if (strict) {
+ isc_throw(BenchMarkError,
+ "load query: unexpected input around line " <<
+ linenum);
+ }
+ continue;
+ }
+
+ // We expect broken lines of data, which will be ignored with a
+ // warning message.
+ try {
+ query_message.clear(Message::RENDER);
+ query_message.setQid(0);
+ query_message.setOpcode(Opcode::QUERY());
+ query_message.setRcode(Rcode::NOERROR());
+ query_message.addQuestion(Question(Name(qname_string), qclass,
+ RRType(qtype_string)));
+
+ renderer.clear();
+ query_message.toWire(renderer);
+ vector<unsigned char> query_data(
+ static_cast<const unsigned char*>(buffer.getData()),
+ static_cast<const unsigned char*>(buffer.getData()) +
+ buffer.getLength());
+ queries.push_back(query_data);
+ } catch (const Exception& error) {
+ if (strict) {
+ isc_throw(BenchMarkError,
+ "failed to parse/create query around line " <<
+ linenum);
+ }
+ continue;
+ }
+ }
+}
+}
+}
diff --git a/src/lib/bench/benchmark_util.h b/src/lib/bench/benchmark_util.h
new file mode 100644
index 0000000000..eebcdc84c5
--- /dev/null
+++ b/src/lib/bench/benchmark_util.h
@@ -0,0 +1,149 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __BENCHMARK_UTIL_H
+#define __BENCHMARK_UTIL_H 1
+
+/// \file
+/// Utilities to help write benchmark cases.
+///
+/// The initial version of this library only contains utilities for very
+/// specific benchmark cases, that is, building DNS query data.
+/// It's not clear if we have more utilities including scenario-independent
+/// ones in future, but we have them here for now.
+/// If we find we only need utilities specific to individual benchmark
+/// scenarios, we may move them to more specific places.
+/// For example, the query generator may go to benchmarks for DNS server
+/// implementations.
+
+#include <istream>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dns {
+class RRClass;
+}
+
+namespace bench {
+/// \brief An exception that is thrown if an error occurs within the benchmark
+/// module.
+class BenchMarkError : public Exception {
+public:
+ BenchMarkError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief A convenient shortcut type to represent a sequence of query %data
+/// in wire format.
+typedef std::vector<std::vector<unsigned char> > BenchQueries;
+
+/// \brief Load query %data from a file into a vector.
+///
+/// The format of the %data file is a sequence of tuples of query name and
+/// query type. Each line specifies a single tuple. Empty lines and
+/// lines beginning with a pound sign (#) are considered comments and will
+/// be ignored. Example:
+/// \code
+/// # This is a comment line, will be ignored. same for the next line.
+///
+/// www.example.com AAAA
+/// ftp.example.org NS
+/// text.dot.example TXT \endcode
+///
+/// For those who are familiar with BIND 9's queryperf tool, this is the
+/// same as the simplest form of the input file for queryperf.
+///
+/// For each tuple, this function builds a wire-format non recursive DNS
+/// query message, and appends it to the given vector in a form of
+/// a vector of <code>unsigned char</code>.
+///
+/// The resulting vector can be used, e.g., for benchmarking query processing
+/// code without involving disk access or network I/O.
+/// It would be handier than existing tool such as queryperf and can help
+/// measure the "bare" (or the best possible) performance of the query
+/// processing itself.
+///
+/// If this function fails to open the specified file to read the %data,
+/// an exception of class \c BenchMarkError will be thrown.
+/// If it fails to recognize an input line either as a comment or as
+/// a tuple of strings, an exception of class \c BenchMarkError will be
+/// thrown.
+///
+/// By default, this function does not require the strings be a valid
+/// domain name or a valid textual representation of an RR type.
+/// This is because the input %data may be built from a packet dump of
+/// real query samples without validation, which may contain bogus values.
+/// It would make more sense to just ignore the bogus %data than filter
+/// the sample beforehand.
+/// This behavior can be changed by setting the \c strict argument to
+/// \c true, in which case if this function fails to parse the query name
+/// or the type, it will throw an exception of class \c BenchMarkError.
+///
+/// If memory allocation fails during the processing, a corresponding standard
+/// exception will be thrown.
+///
+/// This function only offers the basic exception guarantee. That is, if
+/// exception is thrown from this function, it is not guaranteed that
+/// \c queries keeps the content before this function is called.
+/// It is not so difficult to offer a stronger exception guarantee, but
+/// since this function is used in a limited usage, mainly for testing
+/// purposes, its benefit wouldn't outweigh the implementation complexity.
+///
+/// \param input_file A character string specifying the %data file name.
+/// \param queries A vector wherein the query %data is to be stored.
+/// \param qclass The RR class of the resulting queries. The same RR class
+/// is used for all queries.
+/// \param strict If \c true, apply stricter validation on the query name and
+/// query RR types; otherwise invalid inputs will be ignored.
+void loadQueryData(const char* input_file, BenchQueries& queries,
+ const isc::dns::RRClass& qclass, const bool strict = false);
+
+/// \brief Load query %data from an input stream into a vector.
+///
+/// This version of function is same as
+/// loadQueryData(const char*, BenchQueries&, const isc::dns::RRClass&, const bool)
+/// except it reads the input query sequence from a specified input stream.
+///
+/// This version will be used for a smaller scale test where query %data is
+/// hardcoded in the benchmark source code. For example, we could build
+/// a sequence of wire-format queries via the following code:
+/// \code
+/// vector<QueryParam> queries;
+/// stringstream qstream;
+/// qstream << "www.example.com AAAA" << endl
+/// << "ftp.example.org NS" << endl
+/// << "text.dot.example TXT" << endl;
+/// loadQueryData(qstream, queries, RRClass::IN()); \endcode
+/// This will result in the same sequence of queries as the example using
+/// a %data file shown in the other version of the function.
+///
+/// \param input An input stream object that is to emit the query sequence.
+/// \param queries A vector wherein the query %data is to be stored.
+/// \param qclass The RR class of the resulting queries. The same RR class
+/// is used for all queries.
+/// \param strict If \c true, apply stricter validation on the query name and
+/// query RR types; otherwise invalid inputs will be ignored.
+void loadQueryData(std::istream& input, BenchQueries& queries,
+ const isc::dns::RRClass& qclass, const bool strict = false);
+}
+}
+#endif // __BENCHMARK_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/bench/example/Makefile.am b/src/lib/bench/example/Makefile.am
new file mode 100644
index 0000000000..8257b3576d
--- /dev/null
+++ b/src/lib/bench/example/Makefile.am
@@ -0,0 +1,9 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+
+CLEANFILES = *.gcno *.gcda
+
+noinst_PROGRAMS = search_bench
+search_bench_SOURCES = search_bench.cc
+
+search_bench_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+
diff --git a/src/lib/bench/example/search_bench.cc b/src/lib/bench/example/search_bench.cc
new file mode 100644
index 0000000000..90584be2c2
--- /dev/null
+++ b/src/lib/bench/example/search_bench.cc
@@ -0,0 +1,144 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <unistd.h> // for getpid
+
+#include <cstdlib> // for rand
+#include <algorithm>
+#include <iostream>
+#include <vector>
+#include <set>
+
+#include <exceptions/exceptions.h>
+
+#include <bench/benchmark.h>
+
+using namespace std;
+using namespace isc::bench;
+
+namespace {
+template <bool Sorted>
+class VectorSearchBenchMark {
+public:
+ VectorSearchBenchMark(const vector<int>& data,
+ const vector<int>& keys) :
+ data_(data), keys_(keys)
+ {}
+ unsigned int run() {
+ vector<int>::const_iterator iter;
+ vector<int>::const_iterator end_key = keys_.end();
+ for (iter = keys_.begin(); iter != end_key; ++iter) {
+ if (Sorted) {
+ binary_search(data_.begin(), data_.end(), *iter);
+ } else {
+ find(data_.begin(), data_.end(), *iter);
+ }
+ }
+ return (keys_.size());
+ }
+private:
+ const vector<int>& data_;
+ const vector<int>& keys_;
+};
+
+class SetSearchBenchMark {
+public:
+ SetSearchBenchMark(const set<int>& data, const vector<int>& keys) :
+ data_(data), keys_(keys)
+ {}
+ unsigned int run() {
+ vector<int>::const_iterator iter;
+ vector<int>::const_iterator end_key = keys_.end();
+ for (iter = keys_.begin(); iter != end_key; ++iter) {
+ data_.find(*iter);
+ }
+ return (keys_.size());
+ }
+public: // make it visible to the BenchMark class
+ const set<int>& data_;
+private:
+ const vector<int>& keys_;
+};
+}
+
+namespace isc {
+namespace bench {
+template<>
+void
+BenchMark<SetSearchBenchMark>::setUp() {
+ cout << "Benchmark for searching std::set (size="
+ << target_.data_.size() << ")" << endl;
+}
+}
+}
+
+namespace {
+const int DEFAULT_ITERATION = 100;
+const int DEFAULT_SIZE = 10000;
+
+void
+usage() {
+ cerr << "Usage: search_bench [-n iterations] [-s data_size]" << endl;
+ exit (1);
+}
+}
+
+int
+main(int argc, char* argv[]) {
+ int ch;
+ int iteration = DEFAULT_ITERATION;
+ int size = DEFAULT_SIZE;
+ while ((ch = getopt(argc, argv, "n:s:")) != -1) {
+ switch (ch) {
+ case 'n':
+ iteration = atoi(optarg);
+ break;
+ case 's':
+ size = atoi(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 0) {
+ usage();
+ }
+
+ srand(getpid());
+ vector<int> data_vector;
+ set<int> data_set;
+ vector<int> keys;
+ for (int i = 0; i < size; ++i) {
+ data_vector.push_back(i);
+ data_set.insert(i);
+ keys.push_back(rand() % size);
+ }
+
+ cout << "Benchmark for linear search" << endl;
+ BenchMark<VectorSearchBenchMark<false> >(iteration,
+ VectorSearchBenchMark<false>(
+ data_vector, keys));
+ cout << "Benchmark for binary search" << endl;
+ BenchMark<VectorSearchBenchMark<true> >(iteration,
+ VectorSearchBenchMark<true>(
+ data_vector, keys));
+ BenchMark<SetSearchBenchMark>(iteration,
+ SetSearchBenchMark(data_set, keys));
+ return (0);
+}
diff --git a/src/lib/bench/tests/Makefile.am b/src/lib/bench/tests/Makefile.am
new file mode 100644
index 0000000000..afd250c38e
--- /dev/null
+++ b/src/lib/bench/tests/Makefile.am
@@ -0,0 +1,22 @@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += benchmark_unittest.cc
+run_unittests_SOURCES += loadquery_unittest.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/bench/libbench.la
+run_unittests_LDADD += $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/bench/tests/benchmark_unittest.cc b/src/lib/bench/tests/benchmark_unittest.cc
new file mode 100644
index 0000000000..470d5fd6ba
--- /dev/null
+++ b/src/lib/bench/tests/benchmark_unittest.cc
@@ -0,0 +1,143 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <unistd.h> // for usleep
+
+#include <bench/benchmark.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::bench;
+
+namespace {
+// Our "benchmark" simply sleeps for a short period, and reports a faked
+// number of iterations.
+class TestBenchMark {
+public:
+ TestBenchMark(const int sub_iterations, const int sleep_time) :
+ sub_iterations_(sub_iterations), sleep_time_(sleep_time),
+ setup_completed_(false), teardown_completed_(false)
+ {}
+ unsigned int run() {
+ usleep(sleep_time_);
+ return (sub_iterations_);
+ }
+ const int sub_iterations_;
+ const int sleep_time_;
+ bool setup_completed_;
+ bool teardown_completed_;
+};
+}
+
+namespace isc {
+namespace bench {
+template <>
+void
+BenchMark<TestBenchMark>::setUp() {
+ target_.setup_completed_ = true;
+};
+
+template <>
+void
+BenchMark<TestBenchMark>::tearDown() {
+ target_.teardown_completed_ = true;
+};
+
+// XXX: some compilers cannot find class static constants used in
+// EXPECT_xxx macross, for which we need an explicit definition.
+template <typename T>
+const double BenchMark<T>::TIME_FAILURE;
+}
+}
+
+namespace {
+TEST(BenchMarkTest, run) {
+ // use some uncommon iterations for testing purpose:
+ const int sub_iterations = 23;
+ const int sleep_time = 50000; // will sleep for 50ms
+ // we cannot expect particular accuracy on the measured duration, so
+ // we'll include some conservative margin (20%) and perform range
+ // comparison below.
+ const int duration_margin = 10000; // 10ms
+ const int ONE_MILLION = 1000000;
+
+ // Prerequisite check: since the tests in this case may depend on subtle
+ // timing, it may result in false positives. There are reportedly systems
+ // where usleep() doesn't work as this test expects. So we check the
+ // conditions before the tests, and if it fails skip the tests at the
+ // risk of overlooking possible bugs.
+ struct timeval check_begin, check_end;
+ gettimeofday(&check_begin, NULL);
+ usleep(sleep_time);
+ gettimeofday(&check_end, NULL);
+ check_end.tv_sec -= check_begin.tv_sec;
+ if (check_end.tv_usec >= check_begin.tv_usec) {
+ check_end.tv_usec = check_end.tv_usec - check_begin.tv_usec;
+ } else {
+ check_end.tv_usec = ONE_MILLION + check_begin.tv_usec -
+ check_end.tv_usec;
+ --check_end.tv_sec;
+ }
+ if (check_end.tv_sec != 0 ||
+ sleep_time - duration_margin > check_end.tv_usec ||
+ sleep_time + duration_margin < check_end.tv_usec) {
+ cerr << "Prerequisite check failed. skipping test" << endl;
+ return;
+ }
+
+ TestBenchMark test_bench(sub_iterations, sleep_time);
+ BenchMark<TestBenchMark> bench(1, test_bench, false);
+ // Check pre-test conditions.
+ EXPECT_FALSE(test_bench.setup_completed_);
+ EXPECT_FALSE(test_bench.teardown_completed_);
+
+ bench.run();
+
+ // Check if specialized setup and teardown were performed.
+ EXPECT_TRUE(test_bench.setup_completed_);
+ EXPECT_TRUE(test_bench.teardown_completed_);
+
+ // Check accuracy of the measured statistics.
+ EXPECT_EQ(sub_iterations, bench.getIteration());
+ EXPECT_LT(sleep_time - duration_margin, bench.getDuration() * ONE_MILLION);
+ EXPECT_GT(sleep_time + duration_margin, bench.getDuration() * ONE_MILLION);
+ EXPECT_LT((sleep_time - duration_margin) /
+ static_cast<double>(sub_iterations),
+ bench.getAverageTime() * ONE_MILLION);
+ EXPECT_GT((sleep_time + duration_margin) /
+ static_cast<double>(sub_iterations),
+ bench.getAverageTime() * ONE_MILLION);
+ EXPECT_LT(static_cast<double>(sub_iterations) /
+ (sleep_time + duration_margin),
+ bench.getIterationPerSecond() / ONE_MILLION);
+ EXPECT_GT(static_cast<double>(sub_iterations) /
+ (sleep_time - duration_margin),
+ bench.getIterationPerSecond() / ONE_MILLION);
+}
+
+TEST(BenchMarkTest, runWithNoIteration) {
+ // we'll lie on the number of iteration (0). it will result in
+ // meaningless result, but at least it shouldn't crash.
+ TestBenchMark test_bench(0, 0);
+ BenchMark<TestBenchMark> bench(1, test_bench, false);
+ bench.run();
+ EXPECT_EQ(0, bench.getIteration());
+ // Since the reported iteration is 0, naive calculation of the average
+ // time would cause a division by 0 failure.
+ EXPECT_EQ(bench.TIME_FAILURE, bench.getAverageTime());
+}
+}
diff --git a/src/lib/bench/tests/loadquery_unittest.cc b/src/lib/bench/tests/loadquery_unittest.cc
new file mode 100644
index 0000000000..c0fac69a1d
--- /dev/null
+++ b/src/lib/bench/tests/loadquery_unittest.cc
@@ -0,0 +1,198 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+#include <sstream>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <bench/benchmark_util.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::bench;
+using namespace isc::dns;
+
+namespace {
+typedef pair<string, string> QueryParam;
+
+class LoadQueryTest : public ::testing::Test {
+protected:
+ LoadQueryTest() : query_rrclass(RRClass::IN()) {
+ queries.push_back(QueryParam("www.example.org", "AAAA"));
+ queries.push_back(QueryParam("www.example.com", "A"));
+ queries.push_back(QueryParam("test.example", "NS"));
+ }
+ RRClass query_rrclass;
+ BenchQueries result_queries;
+ vector<QueryParam> queries;
+ stringstream query_stream;
+ static const char* const DATA_DIR;
+};
+
+const char* const LoadQueryTest::DATA_DIR = TEST_DATA_DIR;
+
+class QueryInserter {
+public:
+ QueryInserter(stringstream& stream) : stream_(stream) {}
+ void operator()(const QueryParam& query) {
+ stream_ << query.first << " " << query.second << endl;
+ }
+private:
+ stringstream& stream_;
+};
+
+class QueryChecker {
+public:
+ QueryChecker(const vector<QueryParam>* expected, const RRClass& rrclass) :
+ expected_(expected), rrclass_(rrclass)
+ {
+ if (expected != NULL) {
+ iter_ = expected_->begin();
+ }
+ }
+ void operator()(const vector<unsigned char>& actual_data) {
+ InputBuffer buffer(&actual_data[0], actual_data.size());
+ Message message(Message::PARSE);
+ message.fromWire(buffer);
+
+ // Check if the header part indicates an expected standard query.
+ EXPECT_EQ(0, message.getQid());
+ EXPECT_EQ(Opcode::QUERY(), message.getOpcode());
+ EXPECT_EQ(Rcode::NOERROR(), message.getRcode());
+ EXPECT_EQ(Rcode::NOERROR(), message.getRcode());
+ EXPECT_FALSE(message.getHeaderFlag(MessageFlag::QR()));
+ EXPECT_FALSE(message.getHeaderFlag(MessageFlag::AA()));
+ EXPECT_EQ(1, message.getRRCount(Section::QUESTION()));
+ EXPECT_EQ(0, message.getRRCount(Section::ANSWER()));
+ EXPECT_EQ(0, message.getRRCount(Section::AUTHORITY()));
+ EXPECT_EQ(0, message.getRRCount(Section::ADDITIONAL()));
+
+ // Check if the question matches our original data, if the expected
+ // data is given.
+ if (expected_ != NULL) {
+ ConstQuestionPtr question = *message.beginQuestion();;
+ EXPECT_EQ(Name((*iter_).first), question->getName());
+ EXPECT_EQ(RRType((*iter_).second), question->getType());
+ EXPECT_EQ(rrclass_, question->getClass());
+
+ ++iter_;
+ }
+ }
+private:
+ const vector<QueryParam>* expected_;
+ vector<QueryParam>::const_iterator iter_;
+ const RRClass rrclass_;
+};
+
+TEST_F(LoadQueryTest, load) {
+ for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+
+ loadQueryData(query_stream, result_queries, query_rrclass);
+
+ EXPECT_EQ(queries.size(), result_queries.size());
+ for_each(result_queries.begin(), result_queries.end(),
+ QueryChecker(&queries, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadForCHClass) {
+ for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+ query_rrclass = RRClass::CH();
+
+ loadQueryData(query_stream, result_queries, query_rrclass);
+
+ EXPECT_EQ(queries.size(), result_queries.size());
+ for_each(result_queries.begin(), result_queries.end(),
+ QueryChecker(&queries, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadWithComment) {
+ for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+ // add a comment line. this shouldn't change the result.
+ query_stream << "# this is a comment" << endl;
+ query_stream << endl; // empty line. should be ignored, too.
+
+ loadQueryData(query_stream, result_queries, query_rrclass);
+ EXPECT_EQ(queries.size(), result_queries.size());
+ for_each(result_queries.begin(), result_queries.end(),
+ QueryChecker(&queries, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadWithIncompleteData) {
+ for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+ // RRType is missing. It should be ignored by default.
+ query_stream << "type-is-missing" << endl;
+
+ loadQueryData(query_stream, result_queries, query_rrclass);
+ EXPECT_EQ(queries.size(), result_queries.size());
+ for_each(result_queries.begin(), result_queries.end(),
+ QueryChecker(&queries, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadWithIncompleteDataToBeRejected) {
+ for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+ // RRType is missing. We're going to specify the "strict" check, so
+ // we should receive an exception.
+ query_stream << "type-is-missing" << endl;
+ EXPECT_THROW(loadQueryData(query_stream, result_queries, query_rrclass,
+ true), BenchMarkError);
+}
+
+TEST_F(LoadQueryTest, loadWithBadData) {
+ for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+ // invalid RRType. It should be ignored by default.
+ query_stream << "www.example.com NOSUCHRRTYPE" << endl;
+
+ loadQueryData(query_stream, result_queries, query_rrclass);
+ EXPECT_EQ(queries.size(), result_queries.size());
+ for_each(result_queries.begin(), result_queries.end(),
+ QueryChecker(&queries, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadWithBadDataToBeRejected) {
+ for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+ // invalid RRType, which should trigger an exception.
+ query_stream << "www.example.com NOSUCHRRTYPE" << endl;
+ EXPECT_THROW(loadQueryData(query_stream, result_queries, query_rrclass,
+ true), BenchMarkError);
+}
+
+TEST_F(LoadQueryTest, loadFromFile) {
+ const string data_file = string(DATA_DIR) + string("/query.txt");
+ loadQueryData(data_file.c_str(), result_queries, query_rrclass);
+ EXPECT_LT(0, result_queries.size());
+
+ // We are going to skip matching the query data; we only check the header.
+ // We could check the data, too, but to do so we need to populate the
+ // expected data from the file (or prepare a consistent copy locally).
+ // Since the implementation is shared with the stringstream case, the
+ // additional setup wouldn't be worthwhile.
+ for_each(result_queries.begin(), result_queries.end(),
+ QueryChecker(NULL, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadFromFileNotExist) {
+ EXPECT_THROW(loadQueryData("notexistent/query.data", result_queries,
+ query_rrclass), BenchMarkError);
+}
+}
diff --git a/src/lib/bench/tests/run_unittests.cc b/src/lib/bench/tests/run_unittests.cc
new file mode 100644
index 0000000000..ecc077fa77
--- /dev/null
+++ b/src/lib/bench/tests/run_unittests.cc
@@ -0,0 +1,24 @@
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return (RUN_ALL_TESTS());
+}
diff --git a/src/lib/bench/tests/testdata/query.txt b/src/lib/bench/tests/testdata/query.txt
new file mode 100644
index 0000000000..7d66b5436d
--- /dev/null
+++ b/src/lib/bench/tests/testdata/query.txt
@@ -0,0 +1,6 @@
+# This is sample query data for benchmark.
+# The format is the same as BIND 9's queryperf.
+
+www.example.com TXT
+www.example.org SOA
+ftp.example.org RRSIG