#!/usr/bin/perl -w
######################################################################################
# CheckQueryRunTime.pl
# Version 1.02
# Written by: Yuval Baror (http://yuval.bar-or.org)
# Copyright Yuval Baror 2011
######################################################################################
######################################################################################
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# See for a full copy of the
# GNU General Public License
######################################################################################
### Packages #########################################################################
use strict;
use DBI;
use Time::HiRes qw/gettimeofday tv_interval/;
### Globals ##########################################################################
use vars qw/$dbname $dbuser $dbpass $dbh/;
### Function Declarations ############################################################
### Signal Handling ##################################################################
### Main Function ####################################################################
sub main(@)
{
my $printResults = 0;
my $numCacheTimes = 50;
my $numNoCacheTimes = 10;
my $runwayTimes = 5;
# Read the commandline arguments
while (@_ and $_[0] =~ /^-/)
{
my $arg = shift;
if ($arg =~ /^-h(elp)?$/)
{
Usage();
next;
}
elsif ($arg =~ /^-p(rintResults)?$/)
{
$printResults = 1;
next;
}
elsif ($arg =~ /^-n(umTimes)?$/)
{
$numCacheTimes = shift;
$numNoCacheTimes = $numCacheTimes;
next;
}
elsif ($arg =~ /^-n(c|umCacheTimes)?$/)
{
$numCacheTimes = shift;
next;
}
elsif ($arg =~ /^-n(nc|umNoCacheTimes)?$/)
{
$numNoCacheTimes = shift;
next;
}
elsif ($arg =~ /^-r(unwayTimes)?$/)
{
$runwayTimes = shift;
next;
}
warn "Unknown argument '$arg'\n";
Usage();
}
if (scalar(@_) < 4)
{
Usage();
}
my $query;
($dbname, $dbuser, $dbpass, $query) = @_;
Connect();
# Run the query with the cache - start by running it 5 times without storing the
# result to make sure the data is in the cache
print "Preparing to run query with cache\n";
for (my $i = 0; $i < $runwayTimes; ++$i)
{
GetQueryTime($query);
}
print "Running query with cache\n";
my @cacheTimes = ();
my $cacheSum = 0;
my $cacheSumSquares = 0;
for (my $i = 0; $i < $numCacheTimes; ++$i)
{
print ".";
my $result = GetQueryTime($query);
if ($result)
{
push(@cacheTimes, $result);
$cacheSum += $result;
$cacheSumSquares += $result * $result;
}
}
print "\n";
my $cacheAverage = "Invalid";
my $cacheStddev = "Invalid";
my $cacheCount = scalar(@cacheTimes);
if ($cacheCount > 0)
{
$cacheAverage = $cacheSum / $cacheCount;
my $cacheVariance = $cacheSumSquares/$cacheCount - $cacheAverage*$cacheAverage;
$cacheStddev = ($cacheVariance < 0 ? 0 : sqrt($cacheVariance));
}
print "With cache: $cacheAverage ms. (stddev $cacheStddev ms.)\n";
if ($printResults)
{
print "[" . join(", ", @cacheTimes) . "]\n";
}
# Run the query without the cache
print "\nRunning query without cache\n";
my @noCacheTimes = ();
my $noCacheSum = 0;
my $noCacheSumSquares = 0;
for (my $i = 0; $i < $numNoCacheTimes; ++$i)
{
print ".";
ClearCache();
my $result = GetQueryTime($query);
if ($result)
{
push(@noCacheTimes, $result);
$noCacheSum += $result;
$noCacheSumSquares += $result * $result;
}
}
print "\n";
my $noCacheAverage = "Invalid";
my $noCacheStddev = "Invalid";
my $noCacheCount = scalar(@noCacheTimes);
if ($noCacheCount > 0)
{
$noCacheAverage = $noCacheSum / $noCacheCount;
my $noCacheVariance = $noCacheSumSquares/$noCacheCount - $noCacheAverage*$noCacheAverage;
$noCacheStddev = ($noCacheVariance < 0 ? 0 : sqrt($noCacheVariance));
}
print "Without cache: $noCacheAverage ms. (stddev $noCacheStddev ms.)\n";
if ($printResults)
{
print "[" . join(", ", @noCacheTimes) . "]\n";
}
Disconnect();
return 0;
}
exit(main(@ARGV));
######################################################################################
### Subroutines section
######################################################################################
######################################################################################
# Name: Usage
# Purpose: Print a usage message and quit the program
# Arguments: none
# Return Value: none
# Semantics: none
######################################################################################
sub Usage()
{
die "Usage: $0 [-printResults] [-numTimes ] [-numCacheTimes ] [-numNoCacheTimes ] [-runwayTimes ] \n\n" .
"\t-printResults: print the run times of all queries instead of just summaries\n" .
"\t-numTimes: the number of times to run the query both with the cache and without the cache\n" .
"\t-numCacheTimes: the number of times to run the query with the cache\n" .
"\t-numNoCacheTimes: the number of times to run the query without the cache\n" .
"\t-runwayTimes: the number of times to run the query to make sure its data is in the cache\n" .
"\tdbName: the name of the database to connect to\n" .
"\tdbUsername: the username to access the database with\n" .
"\tdbPassword: the password to access the database with\n" .
"\tquery: the query to test\n" .
"\n\nFor example: $0 mydb myuser mypass \"select * from my_table where my_column=17\"\n";
}
# Connect to the DB
sub Connect()
{
$dbh = DBI->connect("DBI:Pg:dbname=$dbname", $dbuser, $dbpass, {AutoCommit => 1, RaiseError => 0});
if (!$dbh) {
die "Cannot connect to Postgres server: $DBI::errstr\n";
}
}
# Disconnect from the DB
sub Disconnect()
{
$dbh->disconnect();
undef $dbh;
}
# Clear the DB cache (actually clearing the OS caches)
sub ClearCache()
{
# Disconnect from the DB
Disconnect();
# Clear the caches
system("service postgresql stop &> /dev/null; sync; echo 3 > /proc/sys/vm/drop_caches; service postgresql start &> /dev/null");
# Reconnect to the DB
Connect();
}
# Get the run time of a query in milliseconds
sub GetQueryTime($)
{
my $query = shift;
my @startTime = gettimeofday();
my $result = $dbh->do($query);
my @endTime = gettimeofday();
if (!$result)
{
print "Error running query: $query\n";
print $dbh->errstr() . "\n";
return 0;
}
return tv_interval(\@startTime, \@endTime) * 1000;
}