ARM, Qt and SQLite
Overview
The last two examples gave a rough overview of how the QSimpleArmTran class can be used in conjunction with simple Qt programs. The next example will show how different ARM implementations (more precisely: QSimpleArmTran and an independent SQLite or MySQL instrumentation) can work together. For this purpose an instrumented SQLite or MySQL database library is needed in addition to the code provided with this example.
The example implements the popular Wisconsin database benchmark. This benchmark is part of every MySQL source distribution (written in perl) and can be downloaded from the MySQL website . The benchmark provided there was used as a basis for the Qt implementation used with this example.
Four generic tables are created in this database, based on the
files onek.data and tenk.data. Some
queries are executed on these tables and the time needed to finish
these queries is measured. When the benchmark finishes the tables
are removed from the database.
Where to get instrumented SQLite
If you received this tutorial with a CD or as an iso image you
will find instrumented SQLite source code in the
archives subdirectory. Otherwise it can be downloaded
from the MyARM website. It is
provided
- either as patch to different versions of sqlite,
- as already patched sqlite source tree or
- as precompiled binary for different platforms.
The code is open source and is provided for free. Contact us if you need a patch to the current SQLite version. At the time when this tutorial is written the Qt-SQLite driver supports version 2.8.16 and all 3.x.x versions of SQLite, so please download one of these and prepare Qt to use one of these as plugin.
How to analyse the measurement data
Before you read the following and get frustrated due to the complexity of the installation: Don't be bothered, it is not that complicated as it seems. To get fast results (just to see that this example is working as expected ) you can use the ARM SDK logger library to see the measurements scrolling down in your console (which is even a good idea to see if you got the Qt SQLite plugin working). Unfortunately this does not allow to analyse your measuremnet data therefore continue reading here.
The main difference to the previous examples is that two different applications try to write their measurement data to the configured database at the same time. Unfortunately this is not possible with a gdbm database used as backend (which is the default configuration with your MyARM installation to get started quickly). Therefore you must install and configure a MySQL database with all necessary MyARM configurations as described in MYARMUG.
When you have done this you must configure your MyARM
environment to use the newly created database. This is done
"automatically" by including the script setup.sh
located in the scripts subdirectory of your MyARM
installation. In this script you will find a line which exports the
variable MYARM_CONFIG_URL.
. ${MYARM_ROOT}/scripts/setup.h mysql.conf
on your shell (it is assumed that the environment variable
MYARM_ROOT is already set. If not check your MyARM
installation).
The result of this tutorial can then be analysed with the MyARM mechanisms described in chapter "How to proceed".
Source code
This is the first chapter where no original example code from Trolltech exists. The source code contains the following headers and implementations.
| wisconsinbenchmark.h | contains the WisconsinBenchmark class definition |
| wisconsinbenchmark.cpp | contains the WisconsinBenchmark class implementation |
| sqltable.h | contains the SqlTable class definition |
| sqltable.cpp | contains the SqlTable class implementation |
| main.cpp | contains only the main loop |
| onek.data | contains the content of the "onek" relations |
| tenk.data | contains the content of the "tenk" relations |
Line-by-Line walkthrough
main.cpp
First lets have a look at the main program. The database is initialised with the following parameters:
db = QSqlDatabase::addDatabase( dbType );
db.setDatabaseName(":memory:");
The type of database used is an SQLite database, i.e.
dbType corresponds to QSQLITE. The
database shall not be written on disk but only in memory (that is
what the :memory: parameter is good for).
The first ARM related lines are:
QSimpleArmTran armTimer("main loop");
WisconsinBenchmark wBench(&armTimer,false);
Here the main transaction definition armTimer is
instantiated, no parent transaction is defined since this is the
root of all following transactions. Right after instantiating the
main transaction the class which is responsible for the execution
of the benchmark is instantiated as well. The false
just specifies that not the whole test should be run since this
would lead to about 30.000 transactions which is a bit too much for
a short tutorial. If you want to have comparable results set this
parameter to true and run the original perl script
from the MySQL benchmarks package against this tutorial. Just a
hint: In case you use MyARM as ARM implementation use the thin
backend in conjunction with the MySQL backend for best performance
results.
This class needs a pointer to the main transaction as parameter.
How this class works is explained in the related sections, here we
just mention that armTimer is used as parent for
almost all of the following transactions defined within
WisconsinBenchmark.
armTimer.start();
wBench.createTables(db);
wBench.insertData();
wBench.executeBenchmark(db);
wBench.deleteTables();
armTimer.stop();
armTimer is then started. The main steps are the
creation of the tables used for the benchmark, the insertion of
data into these tables from onek.data and tenk.data, the execution
of the benchmark itself and the deletion of the tables created
before. The main transaction is stopped when the program
finishes.
Each of the methods explained above uses a transaction definition related to each step (e.g. an "insert-transaction" is used to measure the time needed for inserting the data into the tables).
sqltable.h, sqltable.cpp
SqlTable is a class which mainly functions as an interface to the database tables used by WisconsinBenchmark. The constructor
SqlTable(QSqlDatabase & _db,
QString _name,
QString _tableItems);
creates such a table in the database _db with name
_name and with the variables defined in
_tableItems. _tableItems is a comma
separated list of table items and follows the SQL Syntax, e.g.
odd INT(4),even INT(4).
The method
bool insertData(QString _fileName);
is used to load the data defined in the files
onek.data and tenk.data into the tables.
The files are read line by line and each line is inserted into the
table via the "insert into" query:
myQuery.exec("insert into " +
tableName +
" values (" +
stream.readLine() +
") ");
This line of code was copied from the insertData()
method. The stream mentioned here is the
QTextStream object used to read the file.
wisonsinbenchmark.h, wisconsinbenchmark.cpp
This file contains the class definition of
WisconsinBenchmark. WisconsinBenchmark is the
interface to main and consists mainly of the methods mentioned
above when main.cpp was explained.
WisoncsinBenchmark defines four pointers to transaction objects:
QSimpleArmTran *m_CreateTables;
QSimpleArmTran *m_InsertDataTrans;
QSimpleArmTran *m_ExecuteBench;
QSimpleArmTran *m_DeleteTables;
Each transaction object is instantiated in the constructor of WisconsinBenchmark. And each transaction defines the main transaction as its parent:
m_CreateTables = new QSimpleArmTran("create tables", _mainLoop);
m_InsertDataTrans = new QSimpleArmTran("insert data", _mainLoop);
m_ExecuteBench = new QSimpleArmTran("execute benchmark", _mainLoop);
m_DeleteTables = new QSimpleArmTran("delete tables", _mainLoop);
The names of the transactions speak for themselves. Obviously
they measure the time needed to execute the
WisconsinBenchmark methods mentioned above when
main.cpp was explained.
Besides the transactions, four pointers to SqlTable objects are defined.
SqlTable *m_Onek;
SqlTable *m_Tenk1;
SqlTable *m_Tenk2;
SqlTable *m_Bprime;
These are the tables where the benchmark is executed on. These
tables are created and filled with data from files via the methods
createTables() and insertData(). The
benchmark is then executed by executeBenchmark():
m_ExecuteBench->start();
for(int i = 0;i<m_WsQueries.count();i++)
{
...
executeOneQuery(_db, m_WsQueries[i]);
...
}
m_ExecuteBench->stop();
The "execute-benchmark" transaction is started and stopped here,
i.e. it measures the duration of the whole benchmark. All "select"
queries are executed ten times instead of once. This behaviour was
copied from the perl implementation of the benchmark as it is
delivered with the MySQL source code. It is deactivated in case the
example is only executed with the parameter m_FullTest
set to true. Otherwise each query is only executed once.
The method executeOneQuery() does what its name
implies: It executes one query. This method counts the lines of the
output (although the output is not analysed here). This behaviour
again was copied from the source code delivered with the MySQL
source code.
Compiling
The code can be compiled using the standard Qt qmake mechanism.
Just go into the directory tutorial/t4 and say:
qmake
make
Behaviour
t4 is the name of the program. The method
executeOneQuery() of the class
WisconsinBenchmark does not start and stop any transaction
at all, although it might be very interesting to know how long each
transaction needs. Why? Since this example was selected to show how
different ARM instrumented libraries work together this is done by
the SQLite instrumentation.
|
E x c u r s u s
|
The SQLite or MySQL library used by this example instruments each query via the ARM standard as we do in our Qt examples to measure the Qt environment. This allows us to measure each query by using the SQLite or MySQL instrumentation, while we just measure the time needed for the whole benchmark with the execute-benchmark-transaction. The Qt transaction will be modelled automatically as parent of
all the sqlite-query-transactions. This is achieved via environment
variables. The QSimpleArmTran class fills an environment
variable called So this example shows how easy different independent applications can be measured without knowing each others implementation as long as ARM is used as instrumentation standard. |
The program needs no parameters to work since SQLite does not need any configuration at all (honestly that was the reason why it was selected for this example and is set as the default SQL database). For using a MySQL database the following parameters can be passed:
- --mysql
- specifies to use the MySQL database driver.
- --host name
- specifies the host name where the MySQL database daemon is running.
- --user name
- specifies the user name used to connect to the MySQL database.
- --password word
- specifies the password for the given user.
- --database name
- specifies the database name used within the MySQL database.
Exercise
Due to the database wrappers provided with the Qt framework it might be interesting to see how other SQL databases behaves. Try to switch to a PostgreSQL database instead of SQLite or MySQL.
If our MySQL instrumentation is already available at the time you are reading this you will see that these ARM instrumentations are working together as well. Try to get these running and compare the results with the SQLite instrumentation.
Are you interested in the way SQLite was instrumented? Just have a look at the MyARM website or contact the author of the patch. If you have a look at the source code you will see another possibility to provide data to an ARM application or transaction. The SQLite instrumentation uses so-called properties to save the query text as character string when the query is executed. Remember that ARM metrics do only support numbers but no strings (to be honest: strings with a few characters are supported, but definitely not enough to save a query as text). Since properties are a feature of ARM 4.0 this is further clarified in the next excursus.
|
E x c u r s u s
|
Every ARM object (either application or transaction) is able to store some specific data. Specific in that context means either object definition specific (identity values) or object instance specific (context names). What does that mean in detail? Application identity values do not change during application execution, for instance the name of the running application. Different application instances might exist with the same application identity values. Application context names can change from one application instance to another (although the applications might be derived from the same definition) and are provided during application start. Values are for example thread identifiers in a multi-threaded application, where each thread is modelled as one ARM application. Transaction identity values do not change as long as a transaction "exists", i.e. a transaction might be started and stopped more than once (resulting in many transaction instances) but the transaction identity values are the same for each instance. An example for a transaction identity value is the name of a transaction. Last but not least the transaction context names differ between two transaction instances (although they might be derived from the same transaction definition). An example for a transaction context name is the CPU load at the time when a transaction is started (i.e. the instance is "created") Properties are the preferred ARM mechanism to store the information described above. The only type of data supported are strings (we will come to other types later when we are talk about metrics). Identity Properties are provided as "name=value" pair at the time when an object is defined. The name of a context name ("name=") is also provided during object definition, while the context value is provided during instance creation. Properties provide a strong toolkit to the ARM modeller to describe the environment of his measurement. But properties (especially context names) should be used carefully since string operations are always expensive. |