Parcourir la source

Merge branch 'trac2040'

Tomek Mrugalski il y a 12 ans
Parent
commit
826aa1e57c

+ 65 - 0
tests/tools/dhcp-ubench/Makefile

@@ -0,0 +1,65 @@
+# Linux switches
+CFLAGS=-g -O0 -Wall -pedantic -Wextra
+
+# Mac OS: We don't use pedantic as Mac OS version of MySQL (5.5.24) does use long long (not part of ISO C++)
+#CFLAGS=-g -O0 -Wall -Wextra -I/opt/local/include
+
+# Mac OS does not require -lrt
+# Linux requires -lrt
+LDFLAGS=-lrt
+
+MEMFILE_CFLAGS=
+MEMFILE_LDFLAGS=
+
+# It is mysql_config on most Linux systems and mysql_config5 on Mac OS
+MYSQL_CONFIG=mysql_config
+
+MYSQL_CFLAGS=`$(MYSQL_CONFIG) --cflags`
+MYSQL_LDFLAGS=`$(MYSQL_CONFIG) --libs`
+
+SQLITE_CFLAGS=`pkg-config sqlite3 --cflags`
+SQLITE_LDFLAGS=`pkg-config sqlite3 --libs`
+
+all: mysql_ubench sqlite_ubench memfile_ubench
+
+doc: dhcp-perf-guide.html dhcp-perf-guide.pdf
+
+mysql_ubench.o: mysql_ubench.cc mysql_ubench.h benchmark.h
+	$(CXX) $< -c $(CFLAGS) $(MYSQL_CFLAGS)
+
+benchmark.o: benchmark.cc benchmark.h
+	$(CXX) $< -c $(CFLAGS) $(MYSQL_CFLAGS)
+
+mysql_ubench: mysql_ubench.o benchmark.o
+	$(CXX) $< benchmark.o -o mysql_ubench $(CFLAGS) $(MYSQL_CFLAGS) $(LDFLAGS) $(MYSQL_LDFLAGS)
+
+sqlite_ubench.o: sqlite_ubench.cc sqlite_ubench.h benchmark.h
+	$(CXX) $< -c $(CFLAGS) $(SQLLITE_CFLAGS)
+
+sqlite_ubench: sqlite_ubench.o benchmark.o
+	$(CXX) $< benchmark.o -o sqlite_ubench $(CFLAGS) $(SQLITE_CFLAGS) $(LDFLAGS) $(SQLITE_LDFLAGS)
+
+memfile_ubench.o: memfile_ubench.cc memfile_ubench.h benchmark.h
+	$(CXX) $< -c $(CFLAGS) $(MEMFILE_CFLAGS)
+
+memfile_ubench: memfile_ubench.o benchmark.o
+	$(CXX) $< benchmark.o -o memfile_ubench $(LDFLAGS) $(MEMFILE_LDFLAGS)
+
+clean:
+	rm -f mysql_ubench sqlite_ubench memfile_ubench *.o
+
+version.ent:
+	ln -s ../../../doc/version.ent
+
+dhcp-perf-guide.html: dhcp-perf-guide.xml version.ent
+	xsltproc --novalid --xinclude --nonet \
+		-o $@ \
+		--path ../../../doc \
+		--stringparam section.autolabel 1 \
+		--stringparam section.label.includes.component.label 1 \
+		--stringparam html.stylesheet bind10-guide.css \
+		http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl \
+		dhcp-perf-guide.xml
+
+dhcp-perf-guide.pdf: dhcp-perf-guide.xml
+	docbook2pdf $<

+ 10 - 0
tests/tools/dhcp-ubench/README

@@ -0,0 +1,10 @@
+
+ This directory contains benchmarks for various planned and considered database
+ backends for BIND10 DHCP, codename Kea.
+
+ Before using the code, please read DHCP Performance Guide, available in
+ HTML and PDF formats.
+
+ To compile the code, type: make
+
+ To regenerate documentation, type: make doc

+ 198 - 0
tests/tools/dhcp-ubench/benchmark.cc

@@ -0,0 +1,198 @@
+// Copyright (C) 2012 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.
+
+#include <iostream>
+#include <stdlib.h>
+#include <string.h>
+#include <boost/lexical_cast.hpp>
+#include "benchmark.h"
+
+// The following headers are for getting precise time (clock_gettime on Linux or that mac os thingie)
+#include <time.h>
+#include <sys/time.h>
+#ifdef __MACH__
+#include <mach/clock.h>
+#include <mach/mach.h>
+#endif
+
+
+using namespace std;
+
+uBenchmark::uBenchmark(uint32_t iterations, const std::string& dbname,
+                       bool sync /*= false*/, bool verbose /*= true*/,
+                       const std::string& host /* = "" */,
+                       const std::string& user /* = "" */,
+                       const std::string& pass /* = "" */)
+    :num_(iterations), sync_(sync), verbose_(verbose),
+     hostname_(host), user_(user), passwd_(pass), dbname_(dbname),
+     hitratio_(0.9f), compiled_stmt_(true)
+{
+    /// @todo: make compiled statements a configurable parameter
+
+    /// @todo: convert hitratio_ to user-configurable parameter
+
+    memset(ts_, 0, sizeof(ts_));
+}
+
+void uBenchmark::usage() {
+    cout << "This is a benchmark designed to measure expected performance" << endl;
+    cout << "of several backends. This backend identifies itself as:" << endl;
+    printInfo();
+
+    cout << endl << "Possible command-line parameters:" << endl;
+    cout << " -h - help (you are reading this)" << endl;
+    cout << " -m hostname - specifies MySQL server to connect (MySQL backend only)" << endl;
+    cout << " -u username - specifies MySQL user name (MySQL backend only)" << endl;
+    cout << " -p password - specifies MySQL passwod (MySQL backend only)" << endl;
+    cout << " -f name - database or filename (MySQL, SQLite and memfile)" << endl;
+    cout << " -n integer - number of test iterations (MySQL, SQLite and memfile)" << endl;
+    cout << " -s yes|no - synchronous/asynchronous operation (MySQL, SQLite and memfile)" << endl;
+    cout << " -v yes|no - verbose mode (MySQL, SQLite and memfile)" << endl;
+    cout << " -c yes|no - compiled statements (MySQL and SQLite)" << endl;
+
+    exit(EXIT_FAILURE);
+}
+
+void uBenchmark::parseCmdline(int argc, char* const argv[]) {
+    int ch;
+
+    while ((ch = getopt(argc, argv, "hm:u:p:f:n:s:v:c:")) != -1) {
+        switch (ch) {
+        case 'h':
+            usage();
+        case 'm':
+            hostname_ = string(optarg);
+            break;
+        case 'u':
+            user_ = string(optarg);
+            break;
+        case 'p':
+            passwd_ = string(optarg);
+            break;
+        case 'f':
+            dbname_ = string(optarg);
+            break;
+        case 'n':
+            try {
+                num_ = boost::lexical_cast<unsigned int>(optarg);
+            } catch (const boost::bad_lexical_cast &) {
+                cerr << "Failed to parse number of iterations (-n option):"
+                     << optarg << endl;
+                usage();
+            }
+            break;
+        case 'c':
+            compiled_stmt_ = !strcasecmp(optarg, "yes") || !strcmp(optarg, "1");
+            break;
+        case 's':
+            sync_ = !strcasecmp(optarg, "yes") || !strcmp(optarg, "1");
+            break;
+        case 'v':
+            verbose_ = !strcasecmp(optarg, "yes") || !strcmp(optarg, "1");
+            break;
+        default:
+            usage();
+        }
+    }
+}
+
+void uBenchmark::failure(const char* operation) {
+    cout << "Error during " << operation << endl;
+    throw string(operation);
+}
+
+void uBenchmark::printClock(const std::string& operation, uint32_t num,
+                            const struct timespec& before,
+                            const struct timespec& after) {
+    long int tv_sec = after.tv_sec - before.tv_sec;
+
+    long int tv_nsec = after.tv_nsec - before.tv_nsec;
+
+    if (tv_nsec < 0) {
+        tv_sec--;
+        tv_nsec += 1000000000; // 10^9
+    }
+
+    double oneoper = (tv_nsec/1000 + tv_sec*1000000)/num;
+
+    cout << operation << " repeated " << num << " times took "
+         << tv_sec << " s, " << tv_nsec/1000 << " us, 1 operation took "
+         << oneoper << "us (or " << (1000000/oneoper) << " oper/sec)" << endl;
+
+}
+
+int uBenchmark::run() {
+
+    cout << "Starting test. Parameters:" << endl
+         << "Number of iterations : " << num_ << endl
+         << "Sync/async           : " << (sync_ ? "sync" : "async") << endl
+         << "Verbose              : " << (verbose_ ? "verbose" : "quiet") << endl
+         << "Compiled statements  : " << (compiled_stmt_ ? "yes": "no") << endl
+         << "Database name        : " << dbname_ << endl
+         << "MySQL hostname       : " << hostname_ << endl
+         << "MySQL username       : " << user_ << endl
+         << "MySQL password       : " << passwd_ << endl << endl;
+
+
+    srandom(time(NULL));
+
+    try {
+        connect();
+
+        ts_[0] = getTime();
+
+        createLease4Test();
+        ts_[1] = getTime();
+
+        searchLease4Test();
+        ts_[2] = getTime();
+
+        updateLease4Test();
+        ts_[3] = getTime();
+
+        deleteLease4Test();
+        ts_[4] = getTime();
+
+        disconnect();
+
+    } catch (const std::string& e) {
+        cout << "Failed: " << e << endl;
+        return (-1);
+    }
+
+    printClock("Create leases4", num_, ts_[0], ts_[1]);
+    printClock("Search leases4", num_, ts_[1], ts_[2]);
+    printClock("Update leases4", num_, ts_[2], ts_[3]);
+    printClock("Delete leases4", num_, ts_[3], ts_[4]);
+
+    return (0);
+}
+
+struct timespec uBenchmark::getTime() {
+    struct timespec ts;
+
+#ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time
+    clock_serv_t cclock;
+    mach_timespec_t mts;
+    host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+    clock_get_time(cclock, &mts);
+    mach_port_deallocate(mach_task_self(), cclock);
+    ts.tv_sec = mts.tv_sec;
+    ts.tv_nsec = mts.tv_nsec;
+#else
+    clock_gettime(CLOCK_REALTIME, &ts);
+#endif
+
+    return ts;
+}

+ 209 - 0
tests/tools/dhcp-ubench/benchmark.h

@@ -0,0 +1,209 @@
+// Copyright (C) 2012 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.
+
+#include <string>
+#include <stdint.h>
+
+#ifndef BENCHMARK_H
+#define BENCHMARK_H
+
+/// @brief Micro-benchmark base class.
+///
+/// This class represents an abstract DHCP database backend benchmark.
+/// It is not intended to be used directly, but serves as a common
+/// denominator for specific backend benchmarks that are derived from
+/// it. Currently there are at least 3 benchmarks implemented that
+/// take advantage of it:
+/// - MySQL (MySQL_uBenchmark)
+/// - SQLite (SQLite_uBenchmark)
+/// - memfile (memfile_uBenchmark)
+class uBenchmark {
+public:
+
+    /// @brief the sole constructor, used by all derivated benchmarks
+    ///
+    /// @param iterations number of iterations of each step (insert, search,
+    ///                   update, delete)
+    /// @param dbname name of the database (that is backend-specific, either
+    ///               filename or DB name)
+    /// @param sync sync or async test mode
+    /// @param verbose should extra logging be enabled?
+    /// @param host some backends (currently only MySQL) need this (optional)
+    /// @param username some backends (currently only MySQL) need this (optional)
+    /// @param pass some backends (currently only MySQL) need this (optional)
+    uBenchmark(uint32_t iterations, const std::string& dbname,
+               bool sync, bool verbose,
+               const std::string& host = "",
+               const std::string& username = "",
+               const std::string& pass = "");
+
+    /// @brief Prints version information about specific backend.
+    ///
+    /// The implementation is provided by the DB-specific class.
+    virtual void printInfo() = 0;
+
+    /// @brief Opens a connection to the database.
+    ///
+    /// The implementation is provided by the DB-specific class.
+    virtual void connect() = 0;
+
+    /// @brief Closes connection to the database.
+    ///
+    /// The implementation is provided by the DB-specific class.
+    virtual void disconnect() = 0;
+
+    /// @brief Benchmarks IPv4 address lease creation.
+    ///
+    /// That benchmark method will be called first.
+    /// It is expected to create specific number of leases,
+    /// as specified by \ref num_ parameter. Following
+    /// methods (searchLease4Test(), updateLease4Test(),
+    /// and deleteLease4Test()) assume that lease creation
+    /// is successful. The benchmark is expected to create leases
+    /// starting from BASE_ADDR4 and ending on BASE_ADDR4 + num_.
+    ///
+    /// The implementation is provided by the DB-specific class.
+    virtual void createLease4Test() = 0;
+
+    /// @brief Benchmarks IPv4 address lease search.
+    ///
+    /// This is the second benchmark in a series of four.
+    /// It is called after createLease4Test(), so it expects that the
+    /// database is populated with at least \ref num_ leases.
+    /// It repeats search for a lease num_ times.
+    ///
+    /// The algorithm randomly picks a lease with \ref hitratio_ (typically 90%)
+    /// chance of finding a lease. During typical DHCP operation the server
+    /// sometimes wants to check if specific lease is assigned or not and the
+    /// lease is sometimes not present (e.g. when randomly trying to pick a new
+    /// lease for a new client or doing confirm). Although rather unlikely,
+    /// cases when searching for non-existing leases may be more costly,
+    /// thus should be modelled.
+    ///
+    /// The implementation is provided by the DB-specific class.
+    virtual void searchLease4Test() = 0;
+
+    /// @brief Benchmarks IPv4 address lease update.
+    ///
+    /// This is the third benchmark in a series of four.
+    /// It is called after createLease4Test(), so it expects that the
+    /// database is populated with at least \ref num_ leases.
+    ///
+    /// In a normal DHCP operation, search and update operations are used
+    /// together, but for the benchmarking purposes they are executed
+    /// separately here. Once a lease is found, it is being updated. Typically
+    /// the update is just changing lease expiration timers, so that is what
+    /// the test does. It exploits the fact that there are num_ leases
+    /// in the database, so it picks randomly an address from
+    /// BASE_ADDR4 ... BASE_ADDR4 + num_ range and has a guarantee for the lease
+    /// to be present.
+    ///
+    /// The implementation is provided by the DB-specific class.
+    virtual void updateLease4Test() = 0;
+
+    /// @brief Benchmarks IPv4 address lease removal.
+    ///
+    /// This is the last benchmark in a series of four.
+    /// It is called after createLease4Test(), so it expects that the
+    /// database is populated with at least \ref num_ leases.
+    ///
+    /// It is expected to iteratively delete all num_ leases from
+    /// the database.
+    ///
+    /// The implementation is provided by the DB-specific class.
+    virtual void deleteLease4Test() = 0;
+
+    /// @brief Utility function for reporting errors.
+    ///
+    /// Benchmarks should call that function when something goes wrong.
+    /// details of the problem must be passed as a parameter. As the benchmark
+    /// is not designed to recover from errors, reporting an error aborts
+    /// benchmark execution.
+    ///
+    /// @param operation description of the operation that caused failure
+    virtual void failure(const char* operation);
+
+    /// @brief Prints elapsed time of a specific operation
+    ///
+    /// This method prints out elapsed time of a specific benchmark, together
+    /// with additional statistics.
+    ///
+    /// @param operation name of the operation (usually create, search, update, delete)
+    /// @param num number or iterations (used for statistics)
+    /// @param before timestamp before execution
+    /// @param after timestamp after execution
+    void printClock(const std::string& operation, uint32_t num,
+                    const struct timespec& before,
+                    const struct timespec& after);
+
+    /// @brief Main benchmark execution routine
+    ///
+    /// This method calls create, search, update and delete benchmarks
+    /// and measures appropriate timestamps in ts_ table.
+    ///
+    /// @return 0 if the run was successful, negative value if detected errors
+    int run();
+
+    /// @brief parses command-line parameters
+    ///
+    /// This method parses command-line parameters and sets up appropriate
+    /// values. It is ok to pass argc, argv from main() here.
+    ///
+    /// This method may not return if -h (help) was specified or invalid
+    /// arguments are passed. Appropriate error and help will be displayed
+    /// and the program will terminate.
+    ///
+    /// @param argc number of arguments
+    /// @param argv array to the arguments
+    void parseCmdline(int argc, char* const argv[]);
+
+protected:
+    /// @brief prints out command-line help (list of parameters + version)
+    void usage();
+
+    /// @brief a wrapper around OS-specific method for getting time
+    struct timespec getTime();
+
+    /// Number of operations (e.g. insert lease num times)
+    uint32_t num_;
+
+    /// Synchronous or asynchonous mode?
+    bool sync_;
+
+    /// Should the test print out extra information?
+    bool verbose_;
+
+    // DB parameters
+    std::string hostname_; // used by MySQL only
+    std::string user_;     // used by MySQL only
+    std::string passwd_;   // used by MySQL only
+    std::string dbname_;   // used by MySQL, SQLite and memfile
+
+    /// @brief hit ratio for search test (must be between 0.0 and 1.0)
+    ///
+    /// This parameter is used in search benchmark. The formula causes the
+    /// search to find something a lease in 90% cases of hit ratio is 0.9.
+    float hitratio_;
+
+    /// benchmarks must generate the leases starting from 1.0.0.0 address
+    const static uint32_t BASE_ADDR4 = 0x01000000;
+
+    /// five timestamps (1 at the beginning and 4 after each step)
+    struct timespec ts_[5];
+
+    /// should compiled statements be used?
+    bool compiled_stmt_;
+};
+
+#endif

Fichier diff supprimé car celui-ci est trop grand
+ 199 - 0
tests/tools/dhcp-ubench/dhcp-perf-guide.html


+ 594 - 0
tests/tools/dhcp-ubench/dhcp-perf-guide.xml

@@ -0,0 +1,594 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
+<!ENTITY mdash  "&#x2014;" >
+<!ENTITY % version SYSTEM "version.ent">
+%version;
+]>
+
+<!--
+ - Copyright (C) 2012  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.
+-->
+
+<book>
+  <?xml-stylesheet href="bind10-guide.css" type="text/css"?>
+
+  <bookinfo>
+    <title>DHCP Performance Guide</title>
+    <!-- <subtitle>Various aspects of DHCP Performance in BIND 10</subtitle> -->
+
+    <copyright>
+      <year>2012</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+    <author>
+      <firstname>Tomasz</firstname>
+      <surname>Mrugalski</surname>
+    </author>
+
+    <abstract>
+      <para>BIND 10 is a framework that features Domain Name System
+      (DNS) suite and Dynamic Host Configuration Protocol (DHCP)
+      servers with development managed by Internet Systems Consortium (ISC).
+      This document describes various aspects of DHCP performance,
+      measurements and tuning. It covers BIND 10 DHCP (codename Kea),
+      existing ISC DHCP4 software, perfdhcp (a DHCP performance
+      measurement tool) and other related topics.</para>
+    </abstract>
+
+    <releaseinfo>This is a companion document for BIND 10 version
+    &__VERSION__;.</releaseinfo>
+
+  </bookinfo>
+
+  <preface>
+    <title>Preface</title>
+
+    <section id="acknowledgements">
+      <title>Acknowledgements</title>
+
+      <para>ISC would like to acknowledge generous support for
+      BIND 10 development of DHCPv4 and DHCPv6 components provided
+      by <ulink url="http://www.comcast.com/">Comcast</ulink>.</para>
+
+    </section>
+
+  </preface>
+
+  <chapter id="intro">
+    <title>Introduction</title>
+    <para>
+      This document is in its early stages of development. It is
+      expected to grow significantly in a near future. It will
+      cover topics like database backend perfomance measurements,
+      pros an cons of various optimization techniques and
+      tools.
+    </para>
+
+  </chapter>
+
+  <chapter id="dhcp4">
+    <title>ISC DHCP 4.x</title>
+    <para>
+      TODO: Write something about ISC DHCP4 here.
+    </para>
+  </chapter>
+
+  <chapter id="kea">
+    <title>Kea</title>
+    <para>
+
+    </para>
+
+    <section>
+      <title>Backend performance evaluation</title>
+      <para>
+        Kea will support several different database backends, using
+        both popular databases (like MySQL or SQLite) and
+        custom-developed solutions (like in-memory database).  BIND 10
+        source code features set of performance microbenchmarks.
+        These are small tools written in C/C++ that simulate expected
+        DHCP server behaviour and evaluate the performance of
+        considered databases. As implemented benchmarks are not really
+        simulating DHCP operation, but rather use set of primitives
+        that can be used by a real server, they are called
+        micro-benchmarks.
+      </para>
+
+      <para>Although there are many operations and data types that
+      server could store in a database, the most frequently used data
+      type is lease information. Although lease information for IPv4
+      and IPv6 differs slightly, it is expected that the performance
+      differences will be minimal between IPv4 and IPv6 lease operations.
+      Therefore each test uses lease4 table for performance measurements.
+      </para>
+
+      <para>All benchmarks are implemented as single threaded applications
+      that take advantage of a single database connection.</para>
+
+      <para>
+        Those benchmarks are stored in tests/tools/dhcp-ubench
+        directory. This directory contains simplified prototypes for
+        various DB back-ends that are planned or considered as a
+        backend engine for BIND10 DHCP.  Athough trivial now, they are
+        expected to evolve into useful tools that will allow users to
+        measure performance in their specific environment.
+      </para>
+
+    <para>
+      Currently the following benchmarks are implemented:
+      <itemizedlist>
+        <listitem><para>in memory+flat file</para></listitem>
+        <listitem><para>SQLite</para></listitem>
+        <listitem><para>MySQL</para></listitem>
+      </itemizedlist>
+    </para>
+
+    <para>
+      As they require additional (sometimes heavy) dependencies, they are not
+      built by default. Actually, their build system is completely separated.
+      It will be eventually merged with the main BIND10 makefile system, but
+      that is a low priority for now.
+    </para>
+
+    <para>
+      All benchmarks will follow the same pattern:
+      <orderedlist>
+        <listitem><para>prepare operation (connect to a database, create a file etc.)</para></listitem>
+        <listitem><para>Measure timestamp 0</para></listitem>
+        <listitem><para>Commit new lease4 (repeated X times)</para></listitem>
+        <listitem><para>Measure timestamp 1</para></listitem>
+        <listitem><para>Search for random lease4 (repeated X times)</para></listitem>
+        <listitem><para>Measure timestamp 2</para></listitem>
+        <listitem><para>Update existing lease4 (repeated X times)</para></listitem>
+        <listitem><para>Measure timestamp 3</para></listitem>
+        <listitem><para>Delete existing lease4 (repeated X times)</para></listitem>
+        <listitem><para>Measure timestamp 4</para></listitem>
+        <listitem><para>Print out statistics, based on X and measured timestamps.</para></listitem>
+      </orderedlist>
+
+      Although this approach does not attempt to simulate actual DHCP server
+      operation that has mix of all steps intervening, it answers the
+      questions about basic database strenghts and weak points. In particular
+      it can show what is the impact of specific DB optimizations, like
+      changing engine, optimizing for writes/reads etc.
+    </para>
+
+    <para>
+      The framework attempts to do the same amount of operations for every
+      backend thus allowing fair complarison between them.
+    </para>
+    </section>
+
+    <section id="mysql-backend">
+      <title>MySQL backend</title>
+      <para>MySQL backend requires MySQL client development libraries. It uses
+      mysql_config tool (that works similar to pkg-config) to discover required
+      compilation and linking options. To install required packages on Ubuntu,
+      use the following command:
+
+      <screen>$ <userinput>sudo apt-get install mysql-client mysql-server libmysqlclient-dev</userinput></screen>
+
+      Make sure that MySQL server is running. Make sure that you have your setup
+      configured so there is a user that is able to modify used database.</para>
+
+      <para>Before running tests, you need to initialize your database. You can
+      use mysql.schema script for that purpose. WARNING: It will drop existing
+      Kea database. Do not run this on your production server. Assuming your
+      MySQL user is kea, you can initialize your test database by:
+
+      <screen>$ <userinput>mysql -u kea -p &lt; mysql.schema</userinput></screen>
+      </para>
+
+      <para>After database is initialized, you are ready to run the test:
+      <screen>$ <userinput>./mysql_ubench</userinput></screen>
+
+      or
+
+      <screen>$ <userinput>./mysql_ubench &gt; results->mysql.txt</userinput></screen>
+
+      Redirecting output to a file is important, because for each operation
+      there is a single character printed to show progress. If you have a slow
+      terminal, this may considerably affect test perfromance. On the other hand,
+      printing something after each operation is required, as poor DB setting
+      may slow down operations to around 20 per second. Observant user is expected
+      to note that initial dots are printed too slowly and abort the test.</para>
+
+      <para>Currently all default parameters are hardcoded. Default values can be
+      overwritten using command line switches. Although all benchmarks take
+      the same list of parameters, some of them are specific to a given backend
+      type. To get a list of supported parameters, run your benchmark with -h option:
+
+      <screen>$ <userinput>./mysql_ubench -h</userinput>
+This is a benchmark designed to measure expected performance
+of several backends. This particular version identifies itself
+as following:
+MySQL client version is 5.5.24
+
+Possible command-line parameters:
+ -h - help (you are reading this)
+ -m hostname - specifies MySQL server to connect (MySQL backend only)
+ -u username - specifies MySQL user name (MySQL backend only)
+ -p password - specifies MySQL passwod (MySQL backend only)
+ -f name - database or filename (MySQL, SQLite and memfile)
+ -n integer - number of test repetitions (MySQL, SQLite and memfile)
+ -s yes|no - synchronous/asynchronous operation (MySQL, SQLite and memfile)
+ -v yes|no - verbose mode (MySQL, SQLite and memfile)
+ -c yes|no - should compiled statements be used (MySQL only)
+</screen>
+
+      </para>
+
+      <section>
+        <title>MySQL tweaks</title>
+
+        <para>One parameter that has huge impact on performance is a a backend engine.
+        You can get a list of engines of your MySQL implementation by using
+
+        <screen>&gt; <userinput>show engines;</userinput></screen>
+
+        in your mysql client. Two notable engines are MyISAM and InnoDB. mysql_ubench will
+        use MyISAM for synchronous mode and InnoDB for asynchronous.</para>
+    </section>
+    </section>
+
+
+    <section id="sqlite-ubench">
+      <title>SQLite-ubench</title>
+      <para>SQLite backend requires both sqlite3 development and run-time package. Their
+      names may vary from system to system, but on Ubuntu 12.04 they are called
+      sqlite3 libsqlite3-dev. To install them, use the following command:
+
+      <screen>&gt; <userinput>sudo apt-get install sqlite3 libsqlite3-dev</userinput></screen>
+
+      Before running the test the database has to be created. Use the following command for that:
+      <screen>&gt; <userinput>cat sqlite.schema | sqlite3 sqlite.db</userinput></screen>
+
+      A new database called sqlite.db will be created. That is the default name used
+      by sqlite_ubench test. If you prefer other name, make sure you update
+      sqlite_ubench.cc accordingly.</para>
+
+      <para>Once the database is created, you can run tests:
+      <screen>&gt; <userinput>./sqlite_ubench</userinput></screen>
+      or
+      <screen>&gt; <userinput>./sqlite_ubench > results-sqlite.txt</userinput></screen>
+      </para>
+
+      <section id="sqlite-tweaks">
+        <title>SQLite tweaks</title>
+        <para>To modify default sqlite_ubench parameters, command line
+        switches can be used. Currently supported parameters are
+        (default values specified in brackets):
+        <orderedlist>
+          <listitem><para>-f filename - name of the database file ("sqlite.db")</para></listitem>
+          <listitem><para>-n num - number of iterations (100)</para></listitem>
+          <listitem><para>-s yes|no - should the operations be performend in synchronous (yes)
+          or asynchronous (no) manner (yes)</para></listitem>
+          <listitem><para>-v yes|no - verbose mode. Should the test print out progress? (yes)</para></listitem>
+          <listitem><para>-c yes|no - compiled statements. Should the SQL statements be precompiled?</para></listitem>
+        </orderedlist>
+        </para>
+
+        <para>SQLite can run in asynchronous or synchronous mode. This
+        mode can be controlled by using sync parameter. It is set
+        using (PRAGMA synchronous = ON or OFF).</para>
+
+        <para>Another tweakable feature is journal mode. It can be
+        turned to several modes of operation. Its value can be
+        modified in SQLite_uBenchmark::connect().  See
+        http://www.sqlite.org/pragma.html#pragma_journal_mode for
+        detailed explanantion.</para>
+      </section>
+    </section>
+
+    <section id="memfile-ubench">
+      <title>memfile-ubench</title>
+      <para>Memfile backend is custom developed prototype backend that
+      somewhat mimics operation of ISC DHCP4. It uses in-memory
+      storage using standard C++ and boost mechanisms (std::map and
+      boost::shared_ptr&lt;&gt;). All database changes are also
+      written to a lease file. That file is strictly write-only. This
+      approach takes advantage of the fact that simple append is faster
+      than edition with potential whole file relocation.</para>
+
+      <section id="memfile-tweaks">
+        <title>memfile tweaks</title>
+        <para>To modify default memfile_ubench parameters, command line
+        switches can be used. Currently supported parameters are
+        (default values specified in brackets):
+        <orderedlist>
+          <listitem><para>-f filename - name of the database file ("dhcpd.leases")</para></listitem>
+          <listitem><para>-n num - number of iterations (100)</para></listitem>
+          <listitem><para>-s yes|no - should the operations be performend in synchronous (yes)
+          or asynchronous (no) manner (yes)</para></listitem>
+          <listitem><para>-v yes|no - verbose mode. Should the test print out progress? (yes)</para></listitem>
+        </orderedlist>
+        </para>
+
+        <para>memfile can run in asynchronous or synchronous mode. This
+        mode can be controlled by using sync parameter. It uses
+        fflush() and fsync() in synchronous mode to make sure that
+        data is not buffered and physically stored on disk.</para>
+      </section>
+    </section>
+
+    <section>
+      <title>Performance measurements</title>
+      <para>This section contains sample results for backend performance measurements,
+      taken using microbenchmarks. Tests were conducted on reasonably powerful machine:
+      <screen>
+CPU: Quad-core Intel(R) Core(TM) i7-2600K CPU @ 3.40GHz (8 logical cores)
+HDD: 1,5TB Seagate Barracuda ST31500341AS 7200rpm (used only one of them), ext4 partition
+OS: Ubuntu 12.04, running kernel 3.2.0-26-generic SMP x86_64
+compiler: g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
+MySQL version: 5.5.24
+SQLite version: 3.7.9sourceid version is 2011-11-01 00:52:41 c7c6050ef060877ebe77b41d959e9df13f8c9b5e</screen>
+      </para>
+
+      <para>Benchmarks were run in two series: synchronous and
+      asynchronous. As those modes offer radically different
+      performances, synchronous mode was conducted for 1000 (one
+      thousand) repetitions and asynchronous mode was conducted for
+      100000 (hundred thousand) repetitions.</para>
+
+      <!-- raw results sync -->
+      <table><title>Synchronous results</title>
+      <tgroup cols='6' align='center' colsep='1' rowsep='1'>
+        <colspec colname='Backend'/>
+        <colspec colname='Num' />
+        <colspec colname='Create'/>
+        <colspec colname='Search'/>
+        <colspec colname='Update'/>
+        <colspec colname='Delete'/>
+        <colspec colname='Average'/>
+        <thead>
+          <row>
+            <entry>Backend</entry>
+            <entry>Operations</entry>
+            <entry>Create</entry>
+            <entry>Search</entry>
+            <entry>Update</entry>
+            <entry>Delete</entry>
+            <entry>Average</entry>
+          </row>
+        </thead>
+        <tbody>
+          <row>
+            <entry>MySQL</entry>
+            <entry>1000</entry>
+            <entry>31.603978s</entry>
+            <entry> 0.116612s</entry>
+            <entry>27.964191s</entry>
+            <entry>27.695209s</entry>
+            <entry>21.844998s</entry>
+          </row>
+
+          <row>
+            <entry>SQLite</entry>
+            <entry>1000</entry>
+            <entry>61.421356s</entry>
+            <entry> 0.033283s</entry>
+            <entry>59.476638s</entry>
+            <entry>56.034150s</entry>
+            <entry>44.241357s</entry>
+          </row>
+
+          <row>
+            <entry>memfile</entry>
+            <entry>1000</entry>
+            <entry>41.711886s</entry>
+            <entry> 0.000724s</entry>
+            <entry>42.267578s</entry>
+            <entry>42.169679s</entry>
+            <entry>31.537467s</entry>
+          </row>
+
+        </tbody>
+      </tgroup>
+      </table>
+
+      <para>Following parameters were measured for asynchronous mode.
+      MySQL and SQLite were run with 100 thousand repetitions. Memfile
+      was run for 1 million repetitions due to much larger performance.</para>
+
+      <!-- raw results async -->
+      <table><title>Asynchronous results</title>
+      <tgroup cols='6' align='center' colsep='1' rowsep='1'>
+        <colspec colname='Backend'/>
+        <colspec colname='Num' />
+        <colspec colname='Create'/>
+        <colspec colname='Search'/>
+        <colspec colname='Update'/>
+        <colspec colname='Delete'/>
+        <colspec colname='Average'/>
+        <thead>
+          <row>
+            <entry>Backend</entry>
+            <entry>Operations</entry>
+            <entry>Create [s]</entry>
+            <entry>Search [s]</entry>
+            <entry>Update [s]</entry>
+            <entry>Delete [s]</entry>
+            <entry>Average [s]</entry>
+          </row>
+        </thead>
+        <tbody>
+          <row>
+            <entry>MySQL</entry>
+            <entry>100000</entry>
+            <entry>10.584842s</entry>
+            <entry>10.386402s</entry>
+            <entry>10.062384s</entry>
+            <entry> 8.890197s</entry>
+            <entry> 9.980956s</entry>
+          </row>
+
+          <row>
+            <entry>SQLite</entry>
+            <entry>100000</entry>
+            <entry> 3.710356s</entry>
+            <entry> 3.159129s</entry>
+            <entry> 2.865354s</entry>
+            <entry> 2.439406s</entry>
+            <entry> 3.043561s</entry>
+          </row>
+
+          <row>
+            <entry>memfile</entry>
+            <entry>1000000 (sic!)</entry>
+            <entry> 6.084131s</entry>
+            <entry> 0.862667s</entry>
+            <entry> 6.018585s</entry>
+            <entry> 5.146704s</entry>
+            <entry> 4.528022s</entry>
+          </row>
+
+        </tbody>
+      </tgroup>
+      </table>
+
+      <para>Presented performance results can be computed into operations per second metrics.
+      It should be noted that due to large differences between various operations (sometime
+      over 3 orders of magnitude), it is difficult to create a simple, readable chart with
+      that data.</para>
+
+      <table id="tbl-perf-results"><title>Estimated performance</title>
+      <tgroup cols='6' align='center' colsep='1' rowsep='1'>
+        <colspec colname='Backend'/>
+        <colspec colname='Create'/>
+        <colspec colname='Search'/>
+        <colspec colname='Update'/>
+        <colspec colname='Delete'/>
+        <colspec colname='Average'/>
+        <thead>
+          <row>
+            <entry>Backend</entry>
+            <entry>Create [oper/s]</entry>
+            <entry>Search [oper/s]</entry>
+            <entry>Update [oper/s]</entry>
+            <entry>Delete [oper/s]</entry>
+            <entry>Average [oper/s]</entry>
+          </row>
+        </thead>
+        <tbody>
+          <row>
+            <entry>MySQL (async)</entry>
+            <entry>9447.47</entry>
+            <entry>9627.97</entry>
+            <entry>9938.00</entry>
+            <entry>11248.34</entry>
+            <entry>10065.45</entry>
+          </row>
+
+          <row>
+            <entry>SQLite (async)</entry>
+            <entry>26951.59</entry>
+            <entry>31654.29</entry>
+            <entry>34899.70</entry>
+            <entry>40993.59</entry>
+            <entry>33624.79</entry>
+          </row>
+
+          <row>
+            <entry>memfile (async)</entry>
+            <entry>164362.01</entry>
+            <entry>1159195.84</entry>
+            <entry>166152.01</entry>
+            <entry>194299.11</entry>
+            <entry>421002.24</entry>
+          </row>
+
+
+          <row>
+            <entry>MySQL (sync)</entry>
+            <entry>31.64</entry>
+            <entry>8575.45</entry>
+            <entry>35.76</entry>
+            <entry>36.11</entry>
+            <entry>2169.74</entry>
+          </row>
+
+          <row>
+            <entry>SQLite (sync)</entry>
+            <entry>16.28</entry>
+            <entry>20045.37</entry>
+            <entry>16.81</entry>
+            <entry>17.85</entry>
+            <entry>7524.08</entry>
+          </row>
+
+          <row>
+            <entry>memfile (sync)</entry>
+            <entry>23.97</entry>
+            <entry>1381215.47</entry>
+            <entry>23.66</entry>
+            <entry>23.71</entry>
+            <entry>345321.70</entry>
+          </row>
+
+        </tbody>
+      </tgroup>
+      </table>
+
+      <mediaobject>
+        <imageobject>
+          <imagedata fileref="performance-results-graph1.png" format="PNG"/>
+        </imageobject>
+        <textobject>
+          <phrase>Performance measurements</phrase>
+        </textobject>
+        <caption>
+          <para>Graphical representation of the performance results
+          presented in table <xref linkend="tbl-perf-results" />.</para>
+        </caption>
+      </mediaobject>
+
+    </section>
+
+    <section>
+      <title>Possible further optimizations</title>
+      <para>
+        For debugging purposes the code was compiled with -g -O0
+        flags. While majority of the time was spent in backend
+        functions (that was probably compiled with -O2 flags), the
+        benchmark code could perform faster, when compiled with -O2,
+        rather than -O0. That is expected to affect memfile benchmark.
+      </para>
+      <para>
+        Currently all operations were conducted on one by one
+        basis. Each operation was treated as a separate
+        transaction. Grouping X operations together will potentially
+        bring almost X fold increase in synchronous operations.
+        Extension for this benchmark in this regard should be considered.
+        That affects only write operations (insert, update and delete). Read
+        operations (search) are expected to be barely affected.
+      </para>
+      <para>
+        Multi-threaded or multi-process benchmark may be considered in
+        the future. It may be somewhat difficult as only some backends
+        support concurrent access.
+      </para>
+    </section>
+
+  </chapter>
+
+  <chapter id="perfdhcp">
+    <title>perfdhcp</title>
+    <para>
+      TODO: Write something about perfdhcp here.
+    </para>
+  </chapter>
+
+</book>

+ 356 - 0
tests/tools/dhcp-ubench/memfile_ubench.cc

@@ -0,0 +1,356 @@
+// Copyright (C) 2012 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.
+
+#include <sstream>
+#include <iostream>
+#include <map>
+#include "memfile_ubench.h"
+
+using namespace std;
+
+
+/// @brief In-memory + lease file database implementation
+///
+/// This is a simplified in-memory database that mimics ISC DHCP4 implementation.
+/// It uses STL and boost: std::map for storage, boost::shared ptr for memory
+/// management. It does use C file operations (fopen, fwrite, etc.), because
+/// C++ streams does not offer any easy way to flush their contents, like
+/// fflush() and fsync() does.
+///
+/// IPv4 address is used as a key in the hash.
+class memfile_LeaseMgr {
+public:
+
+    /// A hash table for Lease4 leases.
+    typedef std::map<uint32_t /* addr */, Lease4Ptr /* lease info */> IPv4Hash;
+
+    /// An iterator for Lease4 hash table.
+    typedef std::map<uint32_t, Lease4Ptr>::iterator leaseIt;
+
+    /// @brief The sole memfile lease manager constructor
+    ///
+    /// @param filename name of the lease file (will be overwritten)
+    /// @param sync should operations be
+    memfile_LeaseMgr(const std::string& filename, bool sync);
+
+    /// @brief Destructor (closes file)
+    ~memfile_LeaseMgr();
+
+    /// @brief adds a lease to the hash
+    ///
+    /// @param lease lease to be added
+    bool addLease(Lease4Ptr lease);
+
+    /// @brief returns existing lease
+    ///
+    /// @param addr address of the searched lease
+    ///
+    /// @return smart pointer to the lease (or NULL if lease is not found)
+    Lease4Ptr getLease(uint32_t addr);
+
+    /// @brief Simplified lease update.
+    ///
+    /// Searches for a lease and then updates its client last transmission
+    /// time. Writes new lease content to lease file (and calls fflush()/fsync(),
+    /// if synchronous operation is enabled).
+    ///
+    /// @param addr IPv4 address
+    /// @param new_cltt New client last transmission time
+    ///
+    /// @return pointer to the updated lease (or NULL)
+    Lease4Ptr updateLease(uint32_t addr, uint32_t new_cltt);
+
+    /// @brief Deletes a lease.
+    ///
+    /// @param addr IPv4 address of the lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists
+    bool deleteLease(uint32_t addr);
+
+protected:
+
+    /// @brief Writes updated lease to a file.
+    ///
+    /// @param lease lease to be written
+    void writeLease(Lease4Ptr lease);
+
+    /// Name of the lease file.
+    std::string filename_;
+
+    /// should we do flush after each operation?
+    bool sync_;
+
+    /// File handle to the open lease file.
+    FILE * file_;
+
+    /// Hash table for IPv4 leases
+    IPv4Hash ip4Hash_;
+};
+
+memfile_LeaseMgr::memfile_LeaseMgr(const std::string& filename, bool sync)
+    : filename_(filename), sync_(sync) {
+    file_ = fopen(filename.c_str(), "w");
+    if (!file_) {
+        throw "Failed to create file " + filename;
+    }
+
+}
+
+memfile_LeaseMgr::~memfile_LeaseMgr() {
+    fclose(file_);
+}
+
+void memfile_LeaseMgr::writeLease(Lease4Ptr lease) {
+    fprintf(file_, "lease %d {\n  hw-addr ", lease->addr);
+    for (std::vector<uint8_t>::const_iterator it = lease->hwaddr.begin();
+         it != lease->hwaddr.end(); ++it) {
+        fprintf(file_, "%02x:", *it);
+    }
+    fprintf(file_, ";\n client-id ");
+    for (std::vector<uint8_t>::const_iterator it = lease->client_id.begin();
+         it != lease->client_id.end(); ++it) {
+        fprintf(file_, "%02x:", *it);
+    }
+    fprintf(file_, ";\n  valid-lifetime %d;\n  recycle-time %d;\n"
+            "  cltt %d;\n  pool-id %d;\n  fixed %s;  hostname %s;\n"
+            "  fqdn_fwd %s;\n  fqdn_rev %s;\n};\n",
+            lease->valid_lft, lease->recycle_time, (int)lease->cltt,
+            lease->pool_id, lease->fixed?"true":"false",
+            lease->hostname.c_str(), lease->fqdn_fwd?"true":"false",
+            lease->fqdn_rev?"true":"false");
+
+    if (sync_) {
+        fflush(file_);
+        fsync(fileno(file_));
+    }
+}
+
+bool memfile_LeaseMgr::addLease(Lease4Ptr lease) {
+    if (ip4Hash_.find(lease->addr) != ip4Hash_.end()) {
+        // there is such an address already in the hash
+        return false;
+    }
+    ip4Hash_.insert(pair<uint32_t, Lease4Ptr>(lease->addr, lease));
+    lease->hostname = "add";
+    writeLease(lease);
+    return (true);
+}
+
+Lease4Ptr memfile_LeaseMgr::getLease(uint32_t addr) {
+    leaseIt x = ip4Hash_.find(addr);
+    if (x != ip4Hash_.end()) {
+        return x->second; // found
+    }
+
+    // not found
+    return Lease4Ptr();
+}
+
+Lease4Ptr memfile_LeaseMgr::updateLease(uint32_t addr, uint32_t new_cltt) {
+    leaseIt x = ip4Hash_.find(addr);
+    if (x != ip4Hash_.end()) {
+        x->second->cltt = new_cltt;
+        x->second->hostname = "update";
+        writeLease(x->second);
+        return x->second;
+    }
+    return Lease4Ptr();
+}
+
+bool memfile_LeaseMgr::deleteLease(uint32_t addr) {
+    leaseIt x = ip4Hash_.find(addr);
+    if (x != ip4Hash_.end()) {
+        x->second->hostname = "delete";
+        writeLease(x->second);
+        ip4Hash_.erase(x);
+        return true;
+    }
+    return false;
+}
+
+memfile_uBenchmark::memfile_uBenchmark(const string& filename,
+                                       uint32_t num_iterations,
+                                       bool sync,
+                                       bool verbose)
+    :uBenchmark(num_iterations, filename, sync, verbose) {
+}
+
+void memfile_uBenchmark::connect() {
+    try {
+        leaseMgr_ = new memfile_LeaseMgr(dbname_, sync_);
+    } catch (const std::string& e) {
+        failure(e.c_str());
+    }
+}
+
+void memfile_uBenchmark::disconnect() {
+    delete leaseMgr_;
+    leaseMgr_ = NULL;
+}
+
+void memfile_uBenchmark::createLease4Test() {
+    if (!leaseMgr_) {
+        throw "No LeaseMgr instantiated.";
+    }
+
+    uint32_t addr = BASE_ADDR4;     // Let's start with 1.0.0.0 address
+    const uint8_t hwaddr_len = 20;  // Not a real field
+    char hwaddr_tmp[hwaddr_len];
+    const uint8_t client_id_len = 128;
+    char client_id_tmp[client_id_len];
+    uint32_t valid_lft = 1000;      // We can use the same value for all leases
+    uint32_t recycle_time = 0;      // Not supported in any foreseeable future,
+                                    //     so keep this as 0
+    time_t cltt = time(NULL);       // Timestamp
+    uint32_t pool_id = 0;           // Let's use pools 0-99
+    bool fixed = false;
+    string hostname("foo");         // Will generate it dynamically
+    bool fqdn_fwd = true;           // Let's pretend to do AAAA update
+    bool fqdn_rev = true;           // Let's pretend to do PTR update
+
+    cout << "CREATE:   ";
+
+    // While we could put the data directly into vector, I would like to
+    // keep the code as  similar to other benchmarks as possible
+    for (uint8_t i = 0; i < hwaddr_len; ++i) {
+        hwaddr_tmp[i] = 'A' + i; // let's make hwaddr consisting of letter
+    }
+    vector<uint8_t> hwaddr(hwaddr_tmp, hwaddr_tmp + hwaddr_len - 1);
+
+    for (uint8_t i = 0; i < client_id_len; i++) {
+        client_id_tmp[i] = 33 + i; // 33 is being the first, non whitespace
+                                   // printable ASCII character
+    }
+    vector<uint8_t> client_id(client_id_tmp, client_id_tmp + client_id_len - 1);
+
+    for (uint32_t i = 0; i < num_; ++i) {
+
+        cltt++;
+
+        Lease4Ptr lease = boost::shared_ptr<Lease4>(new Lease4());
+        lease->addr = addr;
+        lease->hwaddr = hwaddr;
+        lease->client_id = client_id;
+        lease->valid_lft = valid_lft;
+        lease->recycle_time = recycle_time;
+        lease->cltt = cltt;
+        lease->pool_id = pool_id;
+        lease->fixed = fixed;
+        lease->hostname = "foo";
+        lease->fqdn_fwd = fqdn_fwd;
+        lease->fqdn_rev = fqdn_rev;
+
+        if (!leaseMgr_->addLease(lease)) {
+            failure("addLease() failed");
+        } else {
+            if (verbose_) {
+                cout << ".";
+            }
+        };
+
+        addr++;
+    }
+    cout << endl;
+}
+
+void memfile_uBenchmark::searchLease4Test() {
+    if (!leaseMgr_) {
+        throw "No LeaseMgr instantiated.";
+    }
+
+    cout << "RETRIEVE: ";
+
+    for (uint32_t i = 0; i < num_; i++) {
+        uint32_t x = BASE_ADDR4 + random() % int(num_ / hitratio_);
+
+        Lease4Ptr lease = leaseMgr_->getLease(x);
+        if (verbose_) {
+            cout << (lease?".":"X");
+        }
+    }
+
+    cout << endl;
+}
+
+void memfile_uBenchmark::updateLease4Test() {
+    if (!leaseMgr_) {
+        throw "No LeaseMgr instantiated.";
+    }
+
+    cout << "UPDATE:   ";
+
+    time_t cltt = time(NULL);
+
+    for (uint32_t i = 0; i < num_; i++) {
+
+        uint32_t x = BASE_ADDR4 + random() % num_;
+
+        Lease4Ptr lease = leaseMgr_->updateLease(x, cltt);
+        if (!lease) {
+            stringstream tmp;
+            tmp << "UPDATE failed for lease " << hex << x << dec;
+            failure(tmp.str().c_str());
+        }
+        if (verbose_) {
+            cout << ".";
+        }
+    }
+
+    cout << endl;
+}
+
+void memfile_uBenchmark::deleteLease4Test() {
+    if (!leaseMgr_) {
+        throw "No LeaseMgr instantiated.";
+    }
+
+    cout << "DELETE:   ";
+
+    for (uint32_t i = 0; i < num_; i++) {
+
+        uint32_t x = BASE_ADDR4 + i;
+
+        if (!leaseMgr_->deleteLease(x)) {
+            stringstream tmp;
+            tmp << "UPDATE failed for lease " << hex << x << dec;
+            failure(tmp.str().c_str());
+        }
+        if (verbose_) {
+            cout << ".";
+        }
+    }
+
+    cout << endl;
+}
+
+void memfile_uBenchmark::printInfo() {
+    cout << "Memory db (using std::map) + write-only file." << endl;
+}
+
+
+int main(int argc, char * const argv[]) {
+
+    const char * filename = "dhcpd.leases";
+    uint32_t num = 100;
+    bool sync = true;
+    bool verbose = false;
+
+    memfile_uBenchmark bench(filename, num, sync, verbose);
+
+    bench.parseCmdline(argc, argv);
+
+    int result = bench.run();
+
+    return (result);
+}

+ 103 - 0
tests/tools/dhcp-ubench/memfile_ubench.h

@@ -0,0 +1,103 @@
+// Copyright (C) 2012 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.
+
+#include <string>
+#include <fstream>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include "benchmark.h"
+
+/// @brief Structure of the Lease4 that is kept in memory
+struct Lease4 {
+    uint32_t addr; /// IPv4 address
+    std::vector<uint8_t> hwaddr; /// hardware address
+    std::vector<uint8_t> client_id; /// client-identifier
+    uint32_t valid_lft; /// valid lifetime timestamp
+    uint32_t recycle_time; /// timer for keeping lease after expiration/release
+                           /// (currently not used)
+    time_t cltt; /// client last transmission time
+    uint32_t pool_id; /// ID of the pool the lease belongs to
+    bool fixed; /// is this lease fixed?
+    std::string hostname; /// client hostname (may be empty)
+    bool fqdn_fwd; /// did we do AAAA update for this lease?
+    bool fqdn_rev; /// did we do PTR update for this lease?
+    std::string options; /// additional options stored with this lease
+                         /// (currently not used)
+    std::string comments; /// comments on that lease
+                          /// (currently not used)
+};
+
+/// Pointer to a Lease4 structure
+typedef boost::shared_ptr<Lease4> Lease4Ptr;
+
+/// an implementation of in-memory+file database
+/// The actual implementation is in memfile_ubench.cc
+class memfile_LeaseMgr;
+
+/// @brief In-memory + file micro-benchmark.
+///
+/// That is a specific backend implementation. See \ref uBenchmark class for
+/// detailed explanation of its operations. This class uses custom in-memory
+/// pseudo-database and external write-only lease file. That approach simulates
+/// modernized model of ISC DHCP4. It uses standard STL maps together with
+/// shared_ptr from boost library. The "database" is implemented in the Lease
+/// Manager (see \ref LeaseMgr in memfile_ubench.cc). All lease changes are
+/// appended to the end of the file, speeding up the process.
+class memfile_uBenchmark: public uBenchmark {
+public:
+
+    /// @brief The sole memfile benchmark constructor.
+    ///
+    /// @param filename name of the write-only lease file
+    /// @param num_iterations number of iterations
+    /// @param sync should fsync() be called after every file write?
+    /// @param verbose would you like extra logging?
+    memfile_uBenchmark(const std::string& filename,
+                       uint32_t num_iterations, bool sync, bool verbose);
+
+    /// @brief Prints backend info.
+    virtual void printInfo();
+
+    /// @brief Spawns lease manager that create empty lease file, initializes
+    ///        empty STL maps.
+    virtual void connect();
+
+    /// @brief Delete lease manager that closes lease file.
+    virtual void disconnect();
+
+    /// @brief Creates new leases.
+    ///
+    /// See uBenchmark::createLease4Test() for detailed explanation.
+    virtual void createLease4Test();
+
+    /// @brief Searches for existing leases.
+    ///
+    /// See uBenchmark::searchLease4Test() for detailed explanation.
+    virtual void searchLease4Test();
+
+    /// @brief Updates existing leases.
+    ///
+    /// See uBenchmark::updateLease4Test() for detailed explanation.
+    virtual void updateLease4Test();
+
+    /// @brief Deletes existing leases.
+    ///
+    /// See uBenchmark::deleteLease4Test() for detailed explanation.
+    virtual void deleteLease4Test();
+
+protected:
+
+    /// Lease Manager (concrete backend implementation, based on STL maps)
+    memfile_LeaseMgr * leaseMgr_;
+};

+ 86 - 0
tests/tools/dhcp-ubench/mysql.schema

@@ -0,0 +1,86 @@
+DROP DATABASE kea;
+CREATE DATABASE kea;
+
+CONNECT kea;
+
+CREATE TABLE lease4 (
+
+    # Primary key (serial = BININT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE)
+    lease_id SERIAL,
+    addr INT UNSIGNED UNIQUE,
+
+    # The largest hardware address is for Infiniband (20 bytes)
+    hwaddr VARCHAR(20),
+
+    # The largest client-id is DUID in DHCPv6 - up to 128 bytes
+    client_id VARCHAR(128),
+
+    # Expressed in seconds
+    valid_lft INT,
+
+    # Expressed in seconds,
+    recycle_time INT DEFAULT 0,
+
+    cltt TIMESTAMP,
+
+    pool_id int,
+
+    fixed BOOL,
+
+    # DDNS stuff
+    hostname VARCHAR(255),
+    fqdn_fwd BOOL DEFAULT false,
+    fqdn_rev BOOL DEFAULT false,
+
+    options TEXT,
+    comments TEXT
+);
+
+CREATE TABLE lease6 (
+
+    # Primary key (serial = BININT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE)
+    lease_id SERIAL,
+    addr CHAR(16) BYTE UNIQUE,
+
+    # The largest hardware address is for Infiniband (20 bytes)
+    hwaddr VARCHAR(20),
+
+    # The largest client-id is DUID in DHCPv6 - up to 128 bytes
+    client_id VARCHAR(128),
+
+    iaid int unsigned,
+
+    # Used for IA_PD only (tinyint = 0..255)
+    prefix_len TINYINT unsigned,
+
+    # Expressed in seconds
+    preferred_lft INT,
+
+    # Expressed in seconds
+    valid_lft INT,
+
+    # Expressed in seconds,
+    recycle_time INT DEFAULT 0,
+
+    cltt TIMESTAMP,
+
+    pool_id int,
+
+    fixed BOOL DEFAULT false,
+
+    hostname VARCHAR(255),
+    fqdn_fwd BOOL DEFAULT false,
+    fqdn_rev BOOL DEFAULT false,
+
+    options TEXT,
+    comments TEXT
+);
+
+CREATE TABLE host (
+    address BIGINT NULL,
+    address6 BIGINT NULL,
+    prefix6 BIGINT NULL,
+    hostname VARCHAR(255),
+    options TEXT,
+    comments TEXT
+);

+ 651 - 0
tests/tools/dhcp-ubench/mysql_ubench.cc

@@ -0,0 +1,651 @@
+// Copyright (C) 2012 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.
+
+#include <iostream>
+#include <sstream>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <mysql.h>
+
+#include "benchmark.h"
+#include "mysql_ubench.h"
+
+using namespace std;
+
+MySQL_uBenchmark::MySQL_uBenchmark(const string& hostname, const string& user,
+                                   const string& pass, const string& db,
+                                   uint32_t num_iterations, bool sync,
+                                   bool verbose)
+    :uBenchmark(num_iterations, db, sync, verbose, hostname, user, pass),
+     conn_(NULL) {
+
+}
+
+void MySQL_uBenchmark::failure(const char* operation) {
+    stringstream tmp;
+    tmp << "Error " << mysql_errno(conn_) << " during " << operation
+        << ": " << mysql_error(conn_);
+    throw tmp.str();
+}
+
+void MySQL_uBenchmark::connect() {
+
+    conn_ = mysql_init(NULL);
+    if (!conn_) {
+        failure("initializing MySQL library");
+    } else {
+        cout << "MySQL library init successful." << endl;
+    }
+
+    if (!mysql_real_connect(conn_, hostname_.c_str(), user_.c_str(),
+                            passwd_.c_str(), dbname_.c_str(), 0, NULL, 0)) {
+        failure("connecting to MySQL server");
+    } else {
+        cout << "MySQL connection established." << endl;
+    }
+
+    string q = "delete from lease4;";
+    if (mysql_real_query(conn_, q.c_str(), strlen(q.c_str()))) {
+        failure("dropping old lease4 entries.");
+    }
+
+    q = "ALTER TABLE lease4 engine=";
+    if (sync_) {
+        q += "InnoDB";
+    } else {
+        q += "MyISAM";
+    }
+    if (mysql_query(conn_, q.c_str())) {
+        q = "Failed to run query:" + q;
+        failure(q.c_str());
+    }
+}
+
+void MySQL_uBenchmark::disconnect() {
+    if (!conn_) {
+        throw "NULL MySQL connection pointer.";
+    }
+    mysql_close(conn_);
+    conn_ = NULL;
+}
+
+void MySQL_uBenchmark::createLease4Test() {
+    if (!conn_) {
+        throw "Not connected to MySQL server.";
+    }
+
+    uint32_t addr = BASE_ADDR4; // Let's start with 1.0.0.0 address
+    char hwaddr[20];
+    size_t hwaddr_len = 20;    // Not a real field
+    char client_id[128];
+    size_t client_id_len = 128;
+    uint32_t valid_lft = 1000;  // We can use the same value for all leases
+    uint32_t recycle_time = 7;  //    not supported in any foresable future,
+
+    char cltt[48];              // timestamp (specified as text)
+    size_t cltt_len;
+
+    sprintf(cltt, "2012-07-11 15:43:00");
+    cltt_len = strlen(cltt);
+
+    uint32_t pool_id = 1000;       // Let's use pool-ids greater than zero
+    bool fixed = false;
+
+    char hostname[] = "foo";    // Will generate it dynamically
+    size_t hostname_len;
+    hostname_len = strlen(hostname);
+
+    bool fqdn_fwd = true;       // Let's pretend to do AAAA update
+    bool fqdn_rev = true;       // Let's pretend to do PTR update
+
+    cout << "CREATE:   ";
+
+    for (uint8_t i = 0; i < hwaddr_len; i++) {
+        hwaddr[i] = 'A' + i; // let's make hwaddr consisting of letters
+    }
+    hwaddr[19] = 0; // make it is null-terminated
+
+    for (uint8_t i = 0; i < client_id_len; i++) {
+        client_id[i] = 33 + i; // 33 is being the first, non whitespace
+                               // printable ASCII character
+    }
+    client_id[127] = 0; // make it is null-terminated
+
+    MYSQL_STMT * stmt = NULL;
+    MYSQL_BIND bind[11]; // 11 parameters in the insert statement
+
+    if (compiled_stmt_) {
+        // create a statement once
+        stmt = mysql_stmt_init(conn_);
+        if (!stmt) {
+            failure("Unable to create compiled statement, mysql_stmt_init() failed");
+        }
+
+        const char * statement = "INSERT INTO lease4(addr,hwaddr,client_id,"
+            "valid_lft,recycle_time,cltt,pool_id,fixed,hostname,"
+            "fqdn_fwd,fqdn_rev) VALUES(?,?,?,?,?,?,?,?,?,?,?)";
+
+        if (mysql_stmt_prepare(stmt, statement, strlen(statement) )) {
+            failure("Failed to prepare statement, mysql_stmt_prepare() returned non-zero");
+        }
+        int param_cnt = mysql_stmt_param_count(stmt);
+        if (param_cnt != 11) {
+            failure("Parameter count sanity check failed.");
+        }
+
+        memset(bind, 0, sizeof(bind));
+
+        // 1st parameter: IPv4 address
+        bind[0].buffer_type = MYSQL_TYPE_LONG;
+        bind[0].buffer = (&addr);
+        bind[0].is_null = 0;
+        bind[0].length = 0;
+
+        // 2nd parameter: Hardware address
+        bind[1].buffer_type = MYSQL_TYPE_VARCHAR;
+        bind[1].buffer = hwaddr;
+        bind[1].buffer_length = hwaddr_len;
+        bind[1].is_null = 0;
+        bind[1].length = &hwaddr_len;
+
+        // 3rd parameter: Client-id
+        bind[2].buffer_type = MYSQL_TYPE_VARCHAR;
+        bind[2].buffer = client_id;
+        bind[2].buffer_length = client_id_len;
+        bind[2].is_null = 0;
+        bind[2].length = &client_id_len;
+
+        // 4th parameter: valid-lifetime
+        bind[3].buffer_type = MYSQL_TYPE_LONG;
+        bind[3].buffer = (&valid_lft);
+        bind[3].is_null = 0;
+        bind[3].length = 0;
+
+        // 5th parameter: recycle-time
+        bind[4].buffer_type = MYSQL_TYPE_LONG;
+        bind[4].buffer = (&recycle_time);
+        bind[4].is_null = 0;
+        bind[4].length = 0;
+
+        // 6th parameter: cltt
+        bind[5].buffer_type = MYSQL_TYPE_TIMESTAMP;
+        bind[5].buffer = cltt;
+        bind[2].buffer_length = cltt_len;
+        bind[5].is_null = 0;
+        bind[5].length = &cltt_len;
+
+        // 7th parameter: pool-id
+        bind[6].buffer_type = MYSQL_TYPE_LONG;
+        bind[6].buffer = &pool_id;
+        bind[6].is_null = 0;
+        bind[6].length = 0;
+
+        // 8th parameter: fixed
+        bind[7].buffer_type = MYSQL_TYPE_TINY;
+        bind[7].buffer = &fixed;
+        bind[7].is_null = 0;
+        bind[7].length = 0;
+
+        // 9th parameter: hostname
+        bind[8].buffer_type = MYSQL_TYPE_VARCHAR;
+        bind[8].buffer = hostname;
+        bind[8].buffer_length = strlen(hostname);
+        bind[8].is_null = 0;
+        bind[8].length = &hostname_len;
+
+        // 10th parameter: fqdn_fwd
+        bind[9].buffer_type = MYSQL_TYPE_TINY;
+        bind[9].buffer = &fqdn_fwd;
+        bind[9].is_null = 0;
+        bind[9].length = 0;
+
+        // 11th parameter: fqdn_rev
+        bind[10].buffer_type = MYSQL_TYPE_TINY;
+        bind[10].buffer = &fqdn_rev;
+        bind[10].is_null = 0;
+        bind[10].length = 0;
+    }
+
+    for (uint32_t i = 0; i < num_; i++) {
+
+        sprintf(cltt, "2012-07-11 15:43:%02d", i % 60);
+
+
+        addr++;
+
+        if (!compiled_stmt_) {
+            // the first address is 1.0.0.0.
+            char query[2000], * end;
+            strcpy(query, "INSERT INTO lease4(addr,hwaddr,client_id,"
+                   "valid_lft,recycle_time,cltt,pool_id,fixed,hostname,"
+                   "fqdn_fwd,fqdn_rev) VALUES(");
+            end = query + strlen(query);
+            end += sprintf(end, "%u,\'", addr);
+            end += mysql_real_escape_string(conn_, end, hwaddr, hwaddr_len);
+            end += sprintf(end,"\',\'");
+            end += mysql_real_escape_string(conn_, end, client_id, client_id_len);
+            end += sprintf(end, "\',%d,%d,'%s',%d,%s,\'%s\',%s,%s);",
+                           valid_lft, recycle_time, cltt,
+                           pool_id, (fixed?"true":"false"), hostname,
+                           (fqdn_fwd?"true":"false"), (fqdn_rev?"true":"false"));
+            // lease_id field is set automatically
+            // options and comments fields are not set
+
+            unsigned int len = end - query;
+            if (mysql_real_query(conn_, query, len)) {
+                // something failed.
+                failure("INSERT query");
+            }
+        } else {
+            // compiled statement
+
+            if (mysql_stmt_bind_param(stmt, bind)) {
+                failure("Failed to bind parameters: mysql_stmt_bind_param() returned non-zero");
+            }
+
+            if (mysql_stmt_execute(stmt)) {
+                failure("Failed to execute statement: mysql_stmt_execute() returned non-zero");
+            }
+
+        }
+
+        if (verbose_) {
+            cout << ".";
+        }
+    }
+
+    if (compiled_stmt_) {
+        if (mysql_stmt_close(stmt)) {
+            failure("Failed to close compiled statement, mysql_stmt_close returned non-zero");
+        }
+    }
+
+    cout << endl;
+}
+
+void MySQL_uBenchmark::searchLease4Test() {
+    if (!conn_) {
+        throw "Not connected to MySQL server.";
+    }
+
+    cout << "RETRIEVE: ";
+
+    uint32_t addr = 0;
+
+    MYSQL_STMT * stmt = NULL;
+    MYSQL_BIND bind[1]; // just a single element
+    if (compiled_stmt_) {
+        stmt = mysql_stmt_init(conn_);
+        if (!stmt) {
+            failure("Unable to create compiled statement");
+        }
+        const char * statement = "SELECT lease_id,addr,hwaddr,client_id,"
+            "valid_lft, cltt,pool_id,fixed,hostname,fqdn_fwd,fqdn_rev "
+            "FROM lease4 where addr=?";
+        if (mysql_stmt_prepare(stmt, statement, strlen(statement))) {
+           failure("Failed to prepare statement, mysql_stmt_prepare() returned non-zero");
+        }
+        int param_cnt = mysql_stmt_param_count(stmt);
+        if (param_cnt != 1) {
+            failure("Parameter count sanity check failed.");
+        }
+
+        memset(bind, 0, sizeof(bind));
+
+        // 1st parameter: IPv4 address
+        bind[0].buffer_type = MYSQL_TYPE_LONG;
+        bind[0].buffer = (&addr);
+        bind[0].is_null = 0;
+        bind[0].length = 0;
+    }
+
+    for (uint32_t i = 0; i < num_; i++) {
+
+        addr = BASE_ADDR4 + random() % int(num_ / hitratio_);
+
+        if (!compiled_stmt_) {
+            char query[512];
+            sprintf(query, "SELECT lease_id,addr,hwaddr,client_id,valid_lft,"
+                    "cltt,pool_id,fixed,hostname,fqdn_fwd,fqdn_rev "
+                    "FROM lease4 where addr=%d", addr);
+            mysql_real_query(conn_, query, strlen(query));
+
+            MYSQL_RES * result = mysql_store_result(conn_);
+
+            int num_rows = mysql_num_rows(result);
+            int num_fields = mysql_num_fields(result);
+
+            if ( (num_rows > 1) ) {
+                stringstream tmp;
+                tmp << "Search: DB returned " << num_rows << " leases for address "
+                    << hex << addr << dec;
+                failure(tmp.str().c_str());
+            }
+
+            if (num_rows) {
+                if (num_fields == 0) {
+                    failure("Query returned empty set");
+                }
+
+                MYSQL_ROW row = mysql_fetch_row(result);
+
+                // pretend to do something with it
+                if (row[0] == NULL) {
+                    failure("SELECT returned NULL data.");
+                }
+                mysql_free_result(result);
+
+                if (verbose_) {
+                    cout << "."; // hit
+                }
+
+            } else {
+                if (verbose_) {
+                    cout << "x"; // miss
+                }
+            }
+        } else {
+            // compiled statement
+
+            if (mysql_stmt_bind_param(stmt, bind)) {
+                failure("Failed to bind parameters: mysql_stmt_bind_param() returned non-zero");
+            }
+
+            if (mysql_stmt_execute(stmt)) {
+                failure("Failed to execute statement: mysql_stmt_execute() returned non-zero");
+            }
+
+            MYSQL_BIND response[11];
+
+            size_t length[11];
+            my_bool is_null[11];
+            my_bool error[11];
+
+            uint32_t lease_id;
+            uint32_t lease_addr;
+            char hwaddr[20];
+            char client_id[128];
+            uint32_t valid_lft;  // We can use the same value for all leases
+            MYSQL_TIME cltt;
+            uint32_t pool_id;
+            my_bool fixed;
+            char hostname[255];
+            my_bool fqdn_fwd;
+            my_bool fqdn_rev;
+
+            for (int j = 0; j < 11; j++) {
+                response[j].is_null = &is_null[j];
+                response[j].length = &length[j];
+                response[j].error = &error[j];
+            }
+
+            // 1th parameter: lease_id
+            response[0].buffer_type = MYSQL_TYPE_LONG;
+            response[0].buffer = (&lease_id);
+
+            // 2nd parameter: IPv4 address
+            response[1].buffer_type = MYSQL_TYPE_LONG;
+            response[1].buffer = (&lease_addr);
+
+            // 3rd parameter: Hardware address
+            response[2].buffer_type = MYSQL_TYPE_STRING;
+            response[2].buffer = hwaddr;
+            response[2].buffer_length = sizeof(hwaddr);
+
+            // 4th parameter: Client-id
+            response[3].buffer_type = MYSQL_TYPE_STRING;
+            response[3].buffer = &client_id;
+
+            // 5th parameter: valid-lifetime
+            response[4].buffer_type = MYSQL_TYPE_LONG;
+            response[4].buffer = &valid_lft;
+
+            // 6th parameter: cltt
+            response[5].buffer_type = MYSQL_TYPE_TIMESTAMP;
+            response[5].buffer = &cltt;
+
+            // 7th parameter: pool-id
+            response[6].buffer_type = MYSQL_TYPE_LONG;
+            response[6].buffer = &pool_id;
+
+            // 8th parameter: fixed
+            response[7].buffer_type = MYSQL_TYPE_TINY;
+            response[7].buffer = &fixed;
+
+            // 9th parameter: hostname
+            response[8].buffer_type = MYSQL_TYPE_STRING;
+            response[8].buffer = &hostname;
+
+            // 10th parameter: fqdn_fwd
+            response[9].buffer_type = MYSQL_TYPE_TINY;
+            response[9].buffer = &fqdn_fwd;
+
+            // 11th parameter: fqdn_rev
+            response[10].buffer_type = MYSQL_TYPE_TINY;
+            response[10].buffer = &fqdn_rev;
+
+            if (mysql_stmt_bind_result(stmt, response))
+            {
+                cout << "Error:" << mysql_stmt_error(stmt) << endl;
+                failure("mysql_stmt_bind_result() failed");
+            }
+            int num_rows = 0;
+
+            if (!mysql_stmt_fetch(stmt)) {
+                if (lease_addr != addr) {
+                    failure("Returned data is bogus!");
+                }
+                num_rows++;
+            }
+
+            // we could call mysql_stmt_fetch again to check that there are no
+            // other data for us. But there should be exactly one row of data
+            // with specified address.
+
+            if (num_rows) {
+                if (verbose_) {
+                    cout << "."; // hit
+                }
+            } else {
+                if (verbose_) {
+                    cout << "X"; // miss
+                }
+            }
+
+        }
+    }
+
+    if (compiled_stmt_) {
+        if (mysql_stmt_close(stmt)) {
+            failure("Failed to close compiled statement, mysql_stmt_close returned non-zero");
+        }
+    }
+
+    cout << endl;
+}
+
+void MySQL_uBenchmark::updateLease4Test() {
+    if (!conn_) {
+        throw "Not connected to MySQL server.";
+    }
+
+    cout << "UPDATE:   ";
+
+    uint32_t valid_lft = 1002; // just some dummy value
+    char cltt[] = "now()";
+    size_t cltt_len = strlen(cltt);
+    uint32_t addr = 0;
+
+    MYSQL_STMT * stmt = NULL;
+    MYSQL_BIND bind[3];
+    if (compiled_stmt_) {
+        stmt = mysql_stmt_init(conn_);
+        if (!stmt) {
+            failure("Unable to create compiled statement");
+        }
+        const char * statement = "UPDATE lease4 SET valid_lft=?, cltt=? WHERE addr=?";
+        if (mysql_stmt_prepare(stmt, statement, strlen(statement))) {
+           failure("Failed to prepare statement, mysql_stmt_prepare() returned non-zero");
+        }
+        int param_cnt = mysql_stmt_param_count(stmt);
+        if (param_cnt != 3) {
+            failure("Parameter count sanity check failed.");
+        }
+
+        memset(bind, 0, sizeof(bind));
+
+        // 1st parameter: valid lifetime
+        bind[0].buffer_type = MYSQL_TYPE_LONG;
+        bind[0].buffer = &valid_lft;
+
+        // 2nd parameter: cltt
+        bind[1].buffer_type = MYSQL_TYPE_STRING;
+        bind[1].buffer = &cltt;
+        bind[1].buffer_length = cltt_len;
+
+        bind[2].buffer_type = MYSQL_TYPE_LONG;
+        bind[2].buffer = &addr;
+    }
+
+
+    for (uint32_t i = 0; i < num_; i++) {
+
+        addr = BASE_ADDR4 + random() % num_;
+
+        if (!compiled_stmt_) {
+            char query[128];
+            sprintf(query, "UPDATE lease4 SET valid_lft=1002, cltt=now() WHERE addr=%d", addr);
+            mysql_real_query(conn_, query, strlen(query));
+
+        } else {
+            // compiled statement
+            if (mysql_stmt_bind_param(stmt, bind)) {
+                failure("Failed to bind parameters: mysql_stmt_bind_param() returned non-zero");
+            }
+
+            if (mysql_stmt_execute(stmt)) {
+                failure("Failed to execute statement: mysql_stmt_execute() returned non-zero");
+            }
+        }
+
+        if (verbose_) {
+            cout << ".";
+        }
+    }
+
+    if (compiled_stmt_) {
+        if (mysql_stmt_close(stmt)) {
+            failure("Failed to close compiled statement, mysql_stmt_close returned non-zero");
+        }
+    }
+
+    cout << endl;
+}
+
+void MySQL_uBenchmark::deleteLease4Test() {
+    if (!conn_) {
+        throw "Not connected to MySQL server.";
+    }
+
+    cout << "DELETE:   ";
+
+    uint32_t addr = 0;
+
+    MYSQL_STMT * stmt = NULL;
+    MYSQL_BIND bind[1]; // just a single element
+    if (compiled_stmt_) {
+
+        stmt = mysql_stmt_init(conn_);
+        if (!stmt) {
+            failure("Unable to create compiled statement, mysql_stmt_init() failed");
+        }
+
+        const char * statement = "DELETE FROM lease4 WHERE addr=?";
+
+        if (mysql_stmt_prepare(stmt, statement, strlen(statement) )) {
+            failure("Failed to prepare statement, mysql_stmt_prepare() returned non-zero");
+        }
+        int param_cnt = mysql_stmt_param_count(stmt);
+        if (param_cnt != 1) {
+            failure("Parameter count sanity check failed.");
+        }
+
+        memset(bind, 0, sizeof(bind));
+
+        // 1st parameter: IPv4 address
+        bind[0].buffer_type = MYSQL_TYPE_LONG;
+        bind[0].buffer = (&addr);
+        bind[0].is_null = 0;
+        bind[0].length = 0;
+    }
+
+
+    for (uint32_t i = 0; i < num_; i++) {
+
+        addr = BASE_ADDR4 + i;
+
+        if (!compiled_stmt_) {
+            char query[128];
+            sprintf(query, "DELETE FROM lease4 WHERE addr=%d", addr);
+            mysql_real_query(conn_, query, strlen(query));
+        } else {
+            // compiled statement
+            if (mysql_stmt_bind_param(stmt, bind)) {
+                failure("Failed to bind parameters: mysql_stmt_bind_param() returned non-zero");
+            }
+
+            if (mysql_stmt_execute(stmt)) {
+                failure("Failed to execute statement: mysql_stmt_execute() returned non-zero");
+            }
+        }
+
+        if (verbose_) {
+            cout << ".";
+        }
+    }
+
+    if (compiled_stmt_) {
+        if (mysql_stmt_close(stmt)) {
+            failure("Failed to close compiled statement, mysql_stmt_close returned non-zero");
+        }
+    }
+
+    cout << endl;
+}
+
+void MySQL_uBenchmark::printInfo() {
+    cout << "MySQL client version is " << mysql_get_client_info() << endl;
+}
+
+
+int main(int argc, char * const argv[]) {
+
+    const char* hostname ="localhost";  // -m (MySQL server)
+    const char* user = "root";          // -u
+    const char* passwd = "secret";      // -p
+    const char* dbname = "kea";         // -f
+    uint32_t num = 100;                 // -n
+    bool sync = true;                   // -s
+    bool verbose = true;                // -v
+
+    MySQL_uBenchmark bench(hostname, user, passwd, dbname, num, sync, verbose);
+
+    bench.parseCmdline(argc, argv);
+
+    int result = bench.run();
+
+    return (result);
+}

+ 87 - 0
tests/tools/dhcp-ubench/mysql_ubench.h

@@ -0,0 +1,87 @@
+// Copyright (C) 2012 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.
+
+#include <string>
+#include "benchmark.h"
+
+/// @brief MySQL micro-benchmark.
+///
+/// That is a specific backend implementation. See \ref uBenchmark class for
+/// detailed explanation of its operations. This class uses MySQL as database
+/// backend.
+class MySQL_uBenchmark: public uBenchmark {
+public:
+
+    /// @brief The sole MySQL micro-benchmark constructor
+    ///
+    /// To avoid influence of network performance, it is highly recommended
+    /// to run MySQL engine on the same host as benchmark. Thus hostname
+    /// is likely to be "localhost". Make sure that the selected database
+    /// is already created and that it follows expected schema. See mysql.schema
+    /// and isc-dhcp-perf-guide.html for details.
+    ///
+    /// Synchronous operation means using InnDB, async is MyISAM.
+    ///
+    /// @param hostname Name of the host to connect to
+    /// @param user usename used during MySQL connection
+    /// @param pass password used during MySQL connection
+    /// @param db name of the database to connect to
+    /// @param num_iterations number of iterations for basic operations
+    /// @param sync synchronous or asynchronous database writes
+    /// @param verbose should extra information be logged?
+    MySQL_uBenchmark(const std::string& hostname, const std::string& user,
+                     const std::string& pass, const std::string& db,
+                     uint32_t num_iterations, bool sync,
+                     bool verbose);
+
+    /// @brief Prints MySQL version info.
+    virtual void printInfo();
+
+    /// @brief Opens connection to the MySQL database.
+    virtual void connect();
+
+    /// @brief Closes connection to the MySQL database.
+    virtual void disconnect();
+
+    /// @brief Creates new leases.
+    ///
+    /// See uBenchmark::createLease4Test() for detailed explanation.
+    virtual void createLease4Test();
+
+    /// @brief Searches for existing leases.
+    ///
+    /// See uBenchmark::searchLease4Test() for detailed explanation.
+    virtual void searchLease4Test();
+
+    /// @brief Updates existing leases.
+    ///
+    /// See uBenchmark::updateLease4Test() for detailed explanation.
+    virtual void updateLease4Test();
+
+    /// @brief Deletes existing leases.
+    ///
+    /// See uBenchmark::deleteLease4Test() for detailed explanation.
+    virtual void deleteLease4Test();
+
+protected:
+    /// @brief Used to report any database failures.
+    ///
+    /// Compared to its base version in uBenchmark class, this one logs additional
+    /// MySQL specific information using mysql_errno() and mysql_error() functions.
+    /// The outcome is the same: exception is thrown.
+    void failure(const char* operation);
+
+    /// Handle to MySQL database connection.
+    MYSQL* conn_;
+};

BIN
tests/tools/dhcp-ubench/performance-results-graph1.png


BIN
tests/tools/dhcp-ubench/performance-results.ods


+ 85 - 0
tests/tools/dhcp-ubench/sqlite.schema

@@ -0,0 +1,85 @@
+DROP TABLE lease4;
+DROP TABLE lease6;
+DROP TABLE host;
+
+CREATE TABLE lease4 (
+
+    -- Primary key (serial = BININT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE)
+    lease_id SERIAL,
+    addr INT UNSIGNED UNIQUE,
+
+    -- The largest hardware address is for Infiniband (20 bytes)
+    hwaddr VARCHAR(20),
+
+    -- The largest client-id is DUID in DHCPv6 - up to 128 bytes
+    client_id VARCHAR(128),
+
+    -- Expressed in seconds
+    valid_lft INT,
+
+    -- Expressed in seconds,
+    recycle_time INT DEFAULT 0,
+
+    cltt TIMESTAMP,
+
+    pool_id int,
+
+    fixed BOOL,
+
+    -- DDNS stuff
+    hostname VARCHAR(255),
+    fqdn_fwd BOOL DEFAULT false,
+    fqdn_rev BOOL DEFAULT false,
+
+    options TEXT,
+    comments TEXT
+);
+
+CREATE TABLE lease6 (
+
+    -- Primary key (serial = BININT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE)
+    lease_id SERIAL,
+    addr CHAR(16) UNIQUE,
+
+    -- The largest hardware address is for Infiniband (20 bytes)
+    hwaddr VARCHAR(20),
+
+    -- The largest client-id is DUID in DHCPv6 - up to 128 bytes
+    client_id VARCHAR(128),
+
+    iaid int unsigned,
+
+    -- Used for IA_PD only (tinyint = 0..255)
+    prefix_len TINYINT unsigned,
+
+    -- Expressed in seconds
+    preferred_lft INT,
+
+    -- Expressed in seconds
+    valid_lft INT,
+
+    -- Expressed in seconds,
+    recycle_time INT DEFAULT 0,
+
+    cltt TIMESTAMP,
+
+    pool_id int,
+
+    fixed BOOL DEFAULT false,
+
+    hostname VARCHAR(255),
+    fqdn_fwd BOOL DEFAULT false,
+    fqdn_rev BOOL DEFAULT false,
+
+    options TEXT,
+    comments TEXT
+);
+
+CREATE TABLE host (
+    address BIGINT NULL,
+    address6 BIGINT NULL,
+    prefix6 BIGINT NULL,
+    hostname VARCHAR(255),
+    options TEXT,
+    comments TEXT
+);

+ 513 - 0
tests/tools/dhcp-ubench/sqlite_ubench.cc

@@ -0,0 +1,513 @@
+// Copyright (C) 2012 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.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sstream>
+#include <iostream>
+#include <sqlite3.h>
+#include "sqlite_ubench.h"
+
+using namespace std;
+
+SQLite_uBenchmark::SQLite_uBenchmark(const string& filename,
+                                     uint32_t num_iterations,
+                                     bool sync, bool verbose)
+    :uBenchmark(num_iterations, filename, sync, verbose),
+     db_(NULL) {
+
+}
+
+void SQLite_uBenchmark::connect() {
+    int result = sqlite3_open(dbname_.c_str(), &db_);
+    if (result != SQLITE_OK) {
+        failure("Failed to open DB file");
+    }
+
+    result = sqlite3_exec(db_, "DELETE FROM lease4", NULL, NULL, NULL);
+    if (result != SQLITE_OK) {
+        failure("Failed to delete old entries");
+    }
+
+    if (sync_) {
+        sqlite3_exec(db_, "PRAGMA synchronous = ON", NULL, NULL, NULL);
+    } else {
+        sqlite3_exec(db_, "PRAGMA synchronous = OFF", NULL, NULL, NULL);
+    }
+
+    // see http://www.sqlite.org/pragma.html#pragma_journal_mode
+    // for detailed explanation. Available modes: DELETE, TRUNCATE,
+    // PERSIST, MEMORY, WAL, OFF
+    sqlite3_exec(db_, "PRAGMA journal_mode = OFF", NULL, NULL, NULL);
+}
+
+void SQLite_uBenchmark::disconnect() {
+    if (db_) {
+        sqlite3_close(db_);
+        db_ = NULL;
+    } else {
+        throw "Can't close SQLite connection: it was never open.";
+    }
+}
+
+void SQLite_uBenchmark::createLease4Test() {
+    if (!db_) {
+        throw "SQLite connection is closed.";
+    }
+
+    uint32_t addr = BASE_ADDR4;     // Let's start with 1.0.0.0 address
+    const uint8_t hwaddr_len = 20;  // Not a real field
+    char hwaddr[hwaddr_len];
+    const uint8_t client_id_len = 128;
+    char client_id[client_id_len];
+    uint32_t valid_lft = 1000;      // We can use the same value for all leases
+    uint32_t recycle_time = 0;      // Not supported in any foresable future,
+                                    //     so keep this as 0
+    char cltt[48];                  // Timestamp
+    uint32_t pool_id = 0;           // Let's use pools 0-99
+    bool fixed = false;
+    string hostname("foo");         // Will generate it dynamically
+    bool fqdn_fwd = true;           // Let's pretend to do AAAA update
+    bool fqdn_rev = true;           // Let's pretend to do PTR update
+
+    cout << "CREATE:   ";
+
+    for (uint8_t i = 0; i < hwaddr_len; i++) {
+        hwaddr[i] = 65 + i;
+    }
+    hwaddr[19] = 0; // workaround
+
+    for (uint8_t i = 0; i < client_id_len; i++) {
+        client_id[i] = 33 + i;
+    }
+    client_id[6] = 'X'; // there's apostrophe here. It would confuse
+                        // query formatting, let's get rid of it
+    client_id[127] = 0; // workaround
+
+
+    sqlite3_stmt *stmt = NULL;
+    if (compiled_stmt_) {
+        char query[] = "INSERT INTO lease4(addr,hwaddr,client_id,"
+            "valid_lft,recycle_time,cltt,pool_id,fixed,hostname,"
+            "fqdn_fwd,fqdn_rev) VALUES(?001,?002,?003,?004,?005,?006,?007,?008,?009,?010,?011);";
+        int result = sqlite3_prepare_v2(db_, query, strlen(query), &stmt, NULL);
+        if (result != SQLITE_OK) {
+            failure("Failed to compile statement");
+        }
+    }
+
+
+
+    for (uint32_t i = 0; i < num_; i++) {
+
+        sprintf(cltt, "2012-07-11 15:43:%02d", i % 60);
+
+        addr++;
+        char* errorMsg = NULL;
+
+        if (!compiled_stmt_) {
+            // the first address is 1.0.0.0.
+            char query[2000];
+            /// @todo: Encode HWADDR and CLIENT-ID properly
+            sprintf(query, "INSERT INTO lease4(addr,hwaddr,client_id,"
+                    "valid_lft,recycle_time,cltt,pool_id,fixed,hostname,"
+                    "fqdn_fwd,fqdn_rev) VALUES(%u,'%s','%s',%d,%d,'%s',%d,'%s','%s','%s','%s');",
+                    addr, hwaddr, client_id, valid_lft, recycle_time,
+                    cltt, pool_id, (fixed?"true":"false"),
+                    hostname.c_str(), (fqdn_fwd?"true":"false"), (fqdn_rev?"true":"false"));
+
+            int result = sqlite3_exec(db_, query, NULL, 0, &errorMsg);
+
+            if (result != SQLITE_OK) {
+                stringstream tmp;
+                tmp << "INSERT error:" << errorMsg;
+                failure(tmp.str().c_str());
+            }
+        } else {
+            // compiled statement
+            int result = sqlite3_bind_int(stmt, 1, addr);
+            if (result != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 1");
+            }
+
+            result = sqlite3_bind_blob(stmt, 2, hwaddr, hwaddr_len, NULL);
+            if (result != SQLITE_OK) {
+                failure("sqlite3_bind_blob() for column 2");
+            }
+
+            result = sqlite3_bind_blob(stmt, 3, client_id, client_id_len, NULL);
+            if (result != SQLITE_OK) {
+                failure("sqlite3_bind_blob() for column 3");
+            }
+
+            if (sqlite3_bind_int(stmt, 4, valid_lft) != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 4");
+            }
+
+            if (sqlite3_bind_int(stmt, 5, recycle_time) != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 5");
+            }
+
+            if (sqlite3_bind_text(stmt, 6, cltt, strlen(cltt), NULL) != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 6");
+            }
+
+            if (sqlite3_bind_int(stmt, 7, pool_id) != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 7");
+            }
+
+            if (sqlite3_bind_int(stmt, 7, pool_id) != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 7");
+            }
+
+            if (sqlite3_bind_int(stmt, 8, fixed) != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 8");
+            }
+
+            if (sqlite3_bind_text(stmt, 9, hostname.c_str(), hostname.length(), NULL) != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 9");
+            }
+
+            if (sqlite3_bind_int(stmt, 10, fqdn_fwd) != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 10");
+            }
+
+            if (sqlite3_bind_int(stmt, 11, fqdn_rev) != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 11");
+            }
+
+            result = sqlite3_step(stmt);
+
+            if (result != SQLITE_DONE) {
+                failure("Failed to execute INSERT clause");
+            }
+
+            // let's reset the compiled statement, so it can be used in the
+            // next iteration
+            result = sqlite3_reset(stmt);
+            if (result != SQLITE_OK) {
+                failure("Failed to execute sqlite3_reset()");
+            }
+
+        }
+
+        if (verbose_) {
+            cout << ".";
+        }
+    }
+
+    if (compiled_stmt_) {
+        int result = sqlite3_finalize(stmt);
+        if (result != SQLITE_OK) {
+            failure("sqlite3_finalize() failed");
+        }
+    }
+
+    cout << endl;
+}
+
+static int search_callback(void *counter, int argc, char** argv,
+                           char** azColName){
+
+    int* cnt = static_cast<int*>(counter);
+    (*cnt)++;
+
+    char buf[512];
+
+    // retrieved lease can be accessed here
+    for(int i = 0; i < argc; i++){
+        // pretend we do something with returned lease
+        if (argv[i]) {
+            strncpy(buf, azColName[i], 512);
+            strncpy(buf, argv[i], 512);
+        }
+
+        // Uncomment this to print out all contents
+        // cout << azColName[i] << "=" << (argv[i] ? argv[i] : "NULL") << endl;
+    }
+
+    return (0);
+}
+
+void SQLite_uBenchmark::searchLease4Test() {
+    if (!db_) {
+        throw "SQLite connection is closed.";
+    }
+
+    cout << "RETRIEVE: ";
+
+    sqlite3_stmt *stmt = NULL;
+    if (compiled_stmt_) {
+        const char query[] = "SELECT lease_id,addr,hwaddr,client_id,valid_lft,"
+            "cltt,pool_id,fixed,hostname,fqdn_fwd,fqdn_rev "
+            "FROM lease4 where addr=?1";
+
+        int result = sqlite3_prepare_v2(db_, query, strlen(query), &stmt, NULL);
+        if (result != SQLITE_OK) {
+            failure("Failed to compile statement");
+        }
+    }
+
+
+    for (uint32_t i = 0; i < num_; i++) {
+
+        uint32_t addr = BASE_ADDR4 + random() % int(num_ / hitratio_);
+
+        int cnt = 0;
+
+        if (!compiled_stmt_) {
+            char* errorMsg = NULL;
+
+            char query[512];
+            sprintf(query, "SELECT lease_id,addr,hwaddr,client_id,valid_lft,"
+                    "cltt,pool_id,fixed,hostname,fqdn_fwd,fqdn_rev "
+                    "FROM lease4 where addr=%d", addr);
+            int result = sqlite3_exec(db_, query, search_callback, &cnt, &errorMsg);
+            if (result != SQLITE_OK) {
+                stringstream tmp;
+                tmp << "SELECT failed: " << errorMsg;
+                failure(tmp.str().c_str());
+            }
+        } else {
+            // compiled statement
+            int result = sqlite3_bind_int(stmt, 1, addr);
+            if (result != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 1");
+            }
+
+            result = sqlite3_step(stmt);
+            switch (result) {
+            case SQLITE_ROW:
+            {
+                uint32_t lease_addr = sqlite3_column_int(stmt, 1);
+                const void * lease_hwaddr = sqlite3_column_blob(stmt, 2);
+                uint32_t lease_hwaddr_len = sqlite3_column_bytes(stmt, 2);
+                const void * lease_clientid = sqlite3_column_blob(stmt, 3);
+                uint32_t lease_clientid_len = sqlite3_column_bytes(stmt, 3);
+                uint32_t lease_valid_lft = sqlite3_column_int(stmt, 4);
+
+                // cltt
+                const unsigned char *lease_cltt = sqlite3_column_text(stmt, 5);
+
+                uint32_t lease_pool_id = sqlite3_column_int(stmt, 6);
+                uint32_t lease_fixed = sqlite3_column_int(stmt, 7);
+
+                const unsigned char *lease_hostname = sqlite3_column_text(stmt, 8);
+
+                uint32_t lease_fqdn_fwd = sqlite3_column_int(stmt, 9);
+                uint32_t lease_fqdn_rev = sqlite3_column_int(stmt, 10);
+
+                if (lease_addr || lease_hwaddr || lease_hwaddr_len || lease_clientid ||
+                    lease_clientid_len || lease_valid_lft || lease_cltt || lease_pool_id ||
+                    lease_fixed || lease_hostname || lease_fqdn_fwd || lease_fqdn_rev) {
+                    // we don't need this information, we just want to obtain it to measure
+                    // the overhead. That strange if is only to quell compiler/cppcheck
+                    // warning about unused variables.
+
+                    cnt = 1;
+                }
+
+                cnt = 1; // there is at least one row
+                break;
+            }
+            case SQLITE_DONE:
+                cnt = 0; // there are no rows at all (i.e. no such lease)
+                break;
+            default:
+                failure("Failed to execute SELECT clause");
+            }
+
+            // let's reset the compiled statement, so it can be used in the
+            // next iteration
+            result = sqlite3_reset(stmt);
+            if (result != SQLITE_OK) {
+                failure("Failed to execute sqlite3_reset()");
+            }
+        }
+
+        if (verbose_) {
+            cout << (cnt?".":"X");
+        }
+    }
+
+    if (compiled_stmt_) {
+        int result = sqlite3_finalize(stmt);
+        if (result != SQLITE_OK) {
+            failure("sqlite3_finalize() failed");
+        }
+    }
+
+    cout << endl;
+}
+
+void SQLite_uBenchmark::updateLease4Test() {
+    if (!db_) {
+        throw "SQLite connection is closed.";
+    }
+
+    cout << "UPDATE:   ";
+
+    sqlite3_stmt *stmt = NULL;
+    if (compiled_stmt_) {
+        const char query[] = "UPDATE lease4 SET valid_lft=1002, cltt='now' WHERE addr=?1";
+
+        int result = sqlite3_prepare_v2(db_, query, strlen(query), &stmt, NULL);
+        if (result != SQLITE_OK) {
+            failure("Failed to compile statement");
+        }
+    }
+
+    for (uint32_t i = 0; i < num_; i++) {
+
+        uint32_t addr = BASE_ADDR4 + random() % num_;
+
+        if (!compiled_stmt_) {
+            char* errorMsg = NULL;
+            char query[512];
+            sprintf(query, "UPDATE lease4 SET valid_lft=1002, cltt='now' WHERE addr=%d",
+                    addr);
+
+            int result = sqlite3_exec(db_, query, NULL /* no callback here*/, 0, &errorMsg);
+            if (result != SQLITE_OK) {
+                stringstream tmp;
+                tmp << "UPDATE error:" << errorMsg;
+                failure(tmp.str().c_str());
+            }
+        } else {
+
+            int result = sqlite3_bind_int(stmt, 1, addr);
+            if (result != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 1");
+            }
+
+            result = sqlite3_step(stmt);
+            if (result != SQLITE_OK && result != SQLITE_DONE) {
+                failure("Failed to execute sqlite3_step() for UPDATE");
+            }
+
+            // let's reset the compiled statement, so it can be used in the
+            // next iteration
+            result = sqlite3_reset(stmt);
+            if (result != SQLITE_OK) {
+                failure("Failed to execute sqlite3_reset()");
+            }
+
+        }
+
+        if (verbose_) {
+            cout << ".";
+        }
+    }
+
+    if (compiled_stmt_) {
+        int result = sqlite3_finalize(stmt);
+        if (result != SQLITE_OK) {
+            failure("sqlite3_finalize() failed");
+        }
+    }
+
+    cout << endl;
+}
+
+void SQLite_uBenchmark::deleteLease4Test() {
+    if (!db_) {
+        throw "SQLite connection is closed.";
+    }
+
+    cout << "DELETE:   ";
+
+    sqlite3_stmt *stmt = NULL;
+    if (compiled_stmt_) {
+        const char query[] = "DELETE FROM lease4 WHERE addr=?1";
+
+        int result = sqlite3_prepare_v2(db_, query, strlen(query), &stmt, NULL);
+        if (result != SQLITE_OK) {
+            failure("Failed to compile statement");
+        }
+    }
+
+    for (uint32_t i = 0; i < num_; i++) {
+
+        uint32_t addr = BASE_ADDR4 + i;
+        if (!compiled_stmt_) {
+            char* errorMsg = NULL;
+
+            char query[2000];
+            sprintf(query, "DELETE FROM lease4 WHERE addr=%d", addr);
+            int result = sqlite3_exec(db_, query, NULL /* no callback here*/, 0, &errorMsg);
+            if (result != SQLITE_OK) {
+                stringstream tmp;
+                tmp << "DELETE error:" << errorMsg;
+                failure(tmp.str().c_str());
+            }
+        } else {
+            // compiled statement
+
+            int result = sqlite3_bind_int(stmt, 1, addr);
+            if (result != SQLITE_OK) {
+                failure("sqlite3_bind_int() for column 1");
+            }
+
+            result = sqlite3_step(stmt);
+            if (result != SQLITE_OK && result != SQLITE_DONE) {
+                failure("Failed to execute sqlite3_step() for UPDATE");
+            }
+
+            // let's reset the compiled statement, so it can be used in the
+            // next iteration
+            result = sqlite3_reset(stmt);
+            if (result != SQLITE_OK) {
+                failure("Failed to execute sqlite3_reset()");
+            }
+
+        }
+        if (verbose_) {
+            cout << ".";
+        }
+    }
+
+    if (compiled_stmt_) {
+        int result = sqlite3_finalize(stmt);
+        if (result != SQLITE_OK) {
+            failure("sqlite3_finalize() failed");
+        }
+    }
+
+    cout << endl;
+}
+
+void SQLite_uBenchmark::printInfo() {
+    cout << "SQLite version is " << sqlite3_libversion()
+         << "sourceid version is " << sqlite3_sourceid() << endl;
+}
+
+
+
+int main(int argc, char* const argv[]) {
+
+    const char* filename = "sqlite.db";
+    uint32_t num = 100;
+    bool sync = true;
+    bool verbose = true;
+
+    SQLite_uBenchmark bench(filename, num, sync, verbose);
+
+    bench.parseCmdline(argc, argv);
+
+    int result = bench.run();
+
+    return (result);
+}

+ 74 - 0
tests/tools/dhcp-ubench/sqlite_ubench.h

@@ -0,0 +1,74 @@
+// Copyright (C) 2012 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.
+
+#include <string>
+#include "benchmark.h"
+
+/// @brief SQLite benchmark
+///
+/// That is a specific backend implementation. See \ref uBenchmark class for
+/// detailed explanation of its operations. This class uses SQLite as DB backend.
+class SQLite_uBenchmark: public uBenchmark {
+public:
+
+    /// @brief The sole SQL benchmark constructor
+    ///
+    /// DB file must be present and appropriate database schema must
+    /// be used. See sqlite.schema script and isc-dhcp-perf-guide.html
+    /// for details.
+    ///
+    /// sync flag affects "PRAGMA synchronous" to be ON or OFF.
+    ///
+    /// @param filename name of the SQLite DB file. Must be present.
+    /// @param num_iterations number of iterations of basic lease operations
+    /// @param sync should the operations be synchronous or not?
+    /// @param verbose would you like extra details be logged?
+    SQLite_uBenchmark(const std::string& filename,
+                      uint32_t num_iterations,
+                      bool sync, bool verbose);
+
+    /// @brief Prints SQLite version info.
+    virtual void printInfo();
+
+    /// @brief Opens connection to the SQLite database.
+    virtual void connect();
+
+    /// @brief Closes connection to the SQLite database.
+    virtual void disconnect();
+
+    /// @brief Creates new leases.
+    ///
+    /// See uBenchmark::createLease4Test() for detailed explanation.
+    virtual void createLease4Test();
+
+    /// @brief Searches for existing leases.
+    ///
+    /// See uBenchmark::searchLease4Test() for detailed explanation.
+    virtual void searchLease4Test();
+
+    /// @brief Updates existing leases.
+    ///
+    /// See uBenchmark::updateLease4Test() for detailed explanation.
+    virtual void updateLease4Test();
+
+    /// @brief Deletes existing leases.
+    ///
+    /// See uBenchmark::deleteLease4Test() for detailed explanation.
+    virtual void deleteLease4Test();
+
+protected:
+
+    /// Handle to SQLite database connection.
+    sqlite3 *db_;
+};