Perl script to calculate DNS queries per second in BIND (named)

I’m pricing out DNS providers and was asked what our current queries-per-second currently are. Sadly I had no idea. After lots of Googling I decided there was really no good way to get this information so I decided to parse the logfile myself.

First, I turned on logging with timestamp in named.conf:

logging
{
...
        # Query logging 2010-12-07
        channel query-log {
                file "data/queries.log" versions 3 size 10m;
                print-time yes;
        };
        category queries { query-log; };
};
options
{
...
        # Query logging 2010-12-07
        querylog yes;

};

Then I restarted named and had to figure out how to parse the file. My first guess was Perl and it didn’t disappoint. Here’s the hideous mess of code:

parsebind.pl

#!/usr/bin/perl

#use Date::Parse;
use Time::ParseDate;

my $line_num = 0;
my $first_line = 0;
my $oldest_record = 0;
my $line = '';
my $date = 0;

my %query_counter = {};
my @abbr = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );

while () {
	chomp;
	$line = $_;

	#my ($date, $time, $ip, $hostname, $in, $type, $crap) = $line  =~ m/^([\d]{2}-[\w]{3}-[\d]{4}) ([\d]{2}:[\d]{2}:[\d]{2}\.[\d]{3}) client ([\d\.]+)#[\d]*: [\w]+: ([\w\d\.]+) ([\w]+) ([\w]+) ([.]*)/;
	if ($line =~ m/^([\d]{2}-[\w]{3}-[\d]{4}) ([\d]{2}:[\d]{2}:[\d]{2}\.[\d]{3}) client ([\d\.]+)#[\d]*: [\w]+: ([\w\d\.]+) ([\w]+) ([\w]+) ([.]*)/) {
		$date = parsedate("$1 $2");
#		print "$1 $2 => $date\n";
		if ($oldest_record == 0) {
			$oldest_record = $date;
			$first_line = $line_num;
			print "First line:\t$line_num\tdate\t".localtime($date)."\n";
		}
		my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($date);
		$year += 1900;
		$mon += 1;
		my $key = $year . '-' . sprintf('%02d',$mon) . '-' . sprintf('%02d',$mday). ' '.sprintf('%02d',$hour).':00 to '.sprintf('%02d',$hour).':59' ;

		if ($query_counter{$key}) {
			$query_counter{$key}++;
		} else {
			$query_counter{$key} = 1;
		}
	}
	$line_num++;
}

print "Last line:\t$line_num\tdate\t".localtime($date)."\n";

my $elapsed = $date - $oldest_record;
my $net_records = $line_num - $first_line;
my $rate = ($net_records * 1.0) / $elapsed;

print "Rate for $elapsed seconds: ".sprintf('%0.2f',$rate)." per second\n";
print "\n\n";

foreach my $key (sort keys %query_counter) {
	$rate = ($query_counter{$key} * 1.0) / 3600;
	print "$key => $query_counter{$key}, rate ".sprintf('%0.3f',$rate)." queries/sec\n";
}

exit 0;

And the beautiful output:

[root@ns3 ~]# perl ~evan/parsebind.pl /var/named/chroot/var/named/data/queries.log
First line:	308	date	Tue Dec  7 17:56:02 2010
Last line:	41289	date	Tue Dec  7 22:44:01 2010
Rate for 17279 seconds: 2.37 per second


2010-12-07 17:00 to 17:59 => 533, rate 0.148 queries/sec
2010-12-07 18:00 to 18:59 => 9185, rate 2.551 queries/sec
2010-12-07 19:00 to 19:59 => 8618, rate 2.394 queries/sec
2010-12-07 20:00 to 20:59 => 8075, rate 2.243 queries/sec
2010-12-07 21:00 to 21:59 => 8762, rate 2.434 queries/sec
2010-12-07 22:00 to 22:59 => 5808, rate 1.613 queries/sec
HASH(0x112382a0) => , rate 0.000 queries/sec
[root@ns3 ~]#

Not pretty but good enough to get a sense of QPS. Hope it helps someone.

Edit: It occurred to me that you may have more than one domain on a single bind server, and you may want the stats for a single one (rather than all aggregated together, which the above script does). The below version includes the second-level domain in the hash key for the final report:

#!/usr/bin/perl

#use Date::Parse;
use Time::ParseDate;

my $line_num = 0;
my $first_line = 0;
my $oldest_record = 0;
my $line = '';
my $date = 0;

my %query_counter = {};
my @abbr = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );

while () {
	chomp;
	$line = $_;

	#my ($date, $time, $ip, $hostname, $in, $type, $crap) = $line  =~ m/^([\d]{2}-[\w]{3}-[\d]{4}) ([\d]{2}:[\d]{2}:[\d]{2}\.[\d]{3}) client ([\d\.]+)#[\d]*: [\w]+: ([\w\d\.]+) ([\w]+) ([\w]+) ([.]*)/;
	if ($line =~ m/^([\d]{2}-[\w]{3}-[\d]{4}) ([\d]{2}:[\d]{2}:[\d]{2}\.[\d]{3}) client ([\d\.]+)#[\d]*: [\w]+: ([\w\d\.]+) ([\w]+) ([\w]+) ([.]*)/) {
		$date = parsedate("$1 $2");
		my ($domain) = ($4 =~ m/([\w\d\-]+\.[\w]+)$/);
		$domain = lc($domain);
#		print "$1 $2 => $date\n";
		if ($oldest_record == 0) {
			$oldest_record = $date;
			$first_line = $line_num;
			print "First line:\t$line_num\tdate\t".localtime($date)."\n";
		}
		my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($date);
		$year += 1900;
		$mon += 1;
		my $key = $domain .' '.$year . '-' . sprintf('%02d',$mon) . '-' . sprintf('%02d',$mday). ' '.sprintf('%02d',$hour).':00 to '.sprintf('%02d',$hour).':59' ;

		if ($query_counter{$key}) {
			$query_counter{$key}++;
		} else {
			$query_counter{$key} = 1;
		}
	}
	$line_num++;
}

print "Last line:\t$line_num\tdate\t".localtime($date)."\n";

my $elapsed = $date - $oldest_record;
my $net_records = $line_num - $first_line;
my $rate = ($net_records * 1.0) / $elapsed;

print "Rate for $elapsed seconds: ".sprintf('%0.2f',$rate)." per second\n";
print "\n\n";

foreach my $key (sort keys %query_counter) {
	$rate = ($query_counter{$key} * 1.0) / 3600;
	print "$key => $query_counter{$key}, rate ".sprintf('%0.3f',$rate)." queries/sec\n";
}

exit 0;

I suppose this doesn’t account for PTR (reverse DNS) lookups, but oh well.

Edit: I added some lame ASCII graphing functionality to the script, check it out in this post.

Advertisements

One thought on “Perl script to calculate DNS queries per second in BIND (named)”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s