#!/usr/bin/perl
# Written by Tim Ellis, loosely based off of a script by Dale Johnson
#
# Copyright(c)2005 by Tim Ellis
#
# 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 version 2 of the License.
#
# 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.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the
#
# Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
#
# ----------------------------------------------------------------------------
#
# This is a simple daemonised program to do a "show full processlist" very
# quickly, logging all SQL statements into a file, and all statistics for the
# same into another. It is basically a daemonised version of "mytop."
#
# ----------------------------------------------------------------------------
#
# Do these grants for this script on your DB:
#
# grant super,process on *.* to dbmon@'127.0.0.1' identified by 'dbmon'
#
# Of course choose the actual password, not 'dbmon' then change $dbPass below
use strict;
use warnings;
use DBI;
use Digest::MD5 qw/md5_hex/;
use POSIX qw/setsid/;
use POSIX qw/strftime/;
use Time::HiRes qw/gettimeofday usleep/;
# database login information
my $dbUser = "dbmon";
my $dbPass = "dbmon";
my $dbMachine = "127.0.0.1:3306";
# how many milliseconds to sleep in our "show full processlist" loop
# ms # per second
# --- ------------
# 500 = 2/sec
# 200 = 5/sec
# 100 = 10/sec
# 50 = 20/sec
# 25 = 40/sec
# 10 = 100/sec
# This is only an estimate, because the algorithm is a simple sleep for
# X milliseconds between each run. If the run takes 100 milliseconds, the
# actual run interval will be X+100 milliseconds.
my $interval = 25 * 1000;
my $daemon;
my $killLongRunning = 0;
my $noKillVolatile = 1;
my $queryBytes = 1024;
my $killConn = 0;
my $doRotate = 0;
my $logDir;
# process input arguments -- mainly, are we a daemon or what?
if (scalar @ARGV < 1) { doArgs(); exit 0; }
foreach my $arg (@ARGV) {
if ($arg =~ /^-+h(elp)*$/) { doArgs(); exit 0; }
if ($arg =~ /^-+querybytes=(\d+)$/) { $queryBytes = $1; }
if ($arg =~ /^-+daemon$/) { $daemon = 1; }
if ($arg =~ /^-+terminal$/) { $daemon = 0; }
if ($arg =~ /^-+license$/) { doLicense(); exit 0; }
if ($arg =~ /^-+killlong=(\d+)$/) { $killLongRunning = $1; }
if ($arg =~ /^-+host=(.*)$/) { $dbMachine = $1; }
if ($arg =~ /^-+killvolatile$/) { $noKillVolatile = 0; }
if ($arg =~ /^-+killconn$/) { $killConn = 1; }
if ($arg =~ /^-+rotate$/) { $doRotate = 1; }
if ($arg =~ /^-+logdir=(.*)$/) { $logDir = $1; }
}
print STDERR " - Host is $dbMachine\n";
# logging locations
unless(defined $logDir) { $logDir = "/var/log/dbmon"; }
my $logFile = "dbmon-$dbMachine.log";
my $errFile = "dbmon-$dbMachine.err";
my $sqlFile = "dbmon-$dbMachine.sql";
# we'll overwrite this from time to time
my $currTime = strftime ('%Y-%m-%d %H:%M:%S', localtime);
# kill existing monitor
print " - Looking for existing monitor...\n";
my $killCount = 0;
my $pid = `ps auxww | grep "MySQL Query Monitor" | grep $logFile | grep -v grep | head -1 | awk '{print \$2;}'`;
while ($pid && $killCount++ <= 5) {
print " + Killing existing monitor...\n";
`kill -2 $pid`;
usleep 200000;
$pid = `ps auxww | grep "MySQL Query Monitor" | grep $logFile | grep -v grep | head -1 | awk '{print \$2;}'`;
if ($pid) { `kill -9 $pid`; }
usleep 50000;
$pid = `ps auxww | grep "MySQL Query Monitor" | grep $logFile | grep -v grep | head -1 | awk '{print \$2;}'`;
}
if ($killCount > 4) {
print " - Had trouble killing old monitor. Check logs at $logDir\n";
exit (255);
}
# rotate out old log info if user requests it
if ($doRotate) {
my $fileText;
print " - Rotating out log information\n";
doRotate ("$errFile");
doRotate ("$sqlFile");
doRotate ("$logFile");
}
# make the logdir if it doesn't exist
mkdir "$logDir" unless (-d "$logDir");
# open various log files
close (STDIN);
close (STDOUT);
close (STDERR);
open (STDIN, "</dev/null");
open (STDERR, ">>$logDir/$errFile");
open (STDOUT, ">>$logDir/$logFile");
open (SQLLOGFILE, ">>$logDir/$sqlFile");
# set unbuffered output. it is not an accident that
# i choose to select STDOUT last.
select SQLLOGFILE; $| = 1;
select STDERR; $| = 1;
select STDOUT; $| = 1;
print STDOUT "\n";
print STDOUT " - Invocation args: @ARGV\n";
print STDOUT " - Statistics Messages will go into $logDir/$logFile at $currTime\n";
print STDOUT " - MD5 hash-->SQL mapping is in $logDir/$sqlFile\n";
print STDERR "\n";
print STDERR " - Error Messages will go into $logDir/$errFile at $currTime\n";
print SQLLOGFILE "\nmd5-hash\tquery-text\n";
# daemonise!!!
if ($daemon) {
print " - Daemonising...\n";
exit (0) if (fork());
POSIX::setsid();
exit (0) if (fork());
$0 = "$0 killLongRunning=$killLongRunning MySQL Query Monitor - see $logDir/$logFile ; @ARGV";
}
# make database connection
my $dbh;
my $sth;
&connectDb();
my %queryDetails;
my %connectionDetails;
my $digest;
my $queryToPrint;
while (1) {
$sth->execute();
if ($DBI::errstr) {
warn "Database error: $DBI::errstr\n";
# make sure we're connected
$dbh->disconnect();
&connectDb();
}
my @this_dbid = ();
# loop through all processes
while (my @row = $sth->fetchrow()) {
$currTime = strftime ('%Y-%m-%d %H:%M:%S', localtime);
my ($dbid, $user, $host, $dbname, $state, $time, $action, $query) = map { defined $_ ? $_ : "undef" } @row;
# don't pay any attention to these administrative queries
next if ($user eq "System User" || $user eq "nodbmonuser");
next if ($state eq "Sleep" || $state eq "Connect");
next if ($query eq "undef" || $query eq "show full processlist");
# make the query easier to view in logs
$query =~ s/[\t\n\r]/ /g;
# if, in comments, there's a [queryName=Name] or [queryID=id] then use
# that for the hash, not the full query
if ($query =~ /\[queryName=(.+?)\]/ || $query =~ /\[queryID=(.+?)\]/) {
$digest = md5_hex($1);
} else {
# attempt to strip out constants that are variable per query before
# hashing it
my $stripQuery = $query;
$stripQuery =~ s/'.+?'/STR/g;
$stripQuery =~ s/\s+[\d.\-]+\s+/ NUM /g;
$stripQuery =~ s/wbo\d+/wboNN/g;
$digest = md5_hex($stripQuery);
}
# i think just the dbid is enough to uniquely identify the query for
# future identification and killing
my $detailsKey = $dbid;
# store the list of currently-running queries
push @this_dbid, $query;
# logic based on a query we know about
if (exists $queryDetails{$detailsKey}) {
# change in state of query
if ($state ne $queryDetails{$detailsKey}{'state'}) {
printf STDOUT $queryDetails{$detailsKey}{'digest'}
. "\tState\t$currTime\t$user\@$host\t$state\tid="
. $queryDetails{$detailsKey}{'dbid'} . "\t"
. "%3.3f sec\n", gettimeofday() - $queryDetails{$detailsKey}{'time'};
$queryDetails{$detailsKey}{'state'} = $state;
}
# note how long it's been running for future killing
# A state of '' means it's doing nothing currently.
if ($state ne '') {
$queryDetails{$detailsKey}{'uSeconds'} += $interval;
}
# kill long-running queries but only attempt this every 5 seconds
# TODO: should only kill a query once per N seconds but the logic
# must be horked. get many kills for one query.
my $killFlag = 0;
my $connTime = 0;
if (exists $connectionDetails{$detailsKey}) { $connTime = gettimeofday() - $connectionDetails{$detailsKey}; }
if (
($killLongRunning && !$killConn && ($time >= $killLongRunning))
||
($killLongRunning && $killConn && ($connTime >= $killLongRunning))
) {
# only kill queries with case-insensitive "select "
# at the beginning, discounting /*comment*/ stuff
# in them if we are only killing nonvolatile queries
if ($noKillVolatile && $query !~ /^(\/\*.+\*\/)*\s*select /i) {
if (!$queryDetails{$detailsKey}{'tokill'}) {
print STDERR " - $currTime :: Not killing volatile query. digest=$digest query=$query\n";
$queryDetails{$detailsKey}{'tokill'} = int(12500000 / $interval);
}
} elsif ($query =~ /allowtorun=(\d+)/) {
my $secondsAllowed = $1;
if ($time > $secondsAllowed) {
$killFlag = 1;
}
} else {
$killFlag = 1;
}
# if the query won't die, we decrement tokill until it hits zero
# again, at which point the query becomes valid to kill again
if ($queryDetails{$detailsKey}{'tokill'} > 0) {
$queryDetails{$detailsKey}{'tokill'} -= 1;
}
if ($killFlag && !$queryDetails{$detailsKey}{'tokill'}) {
my $runTime = sprintf ('%.2fsec',$queryDetails{$detailsKey}{'uSeconds'} / 1000000);
print STDERR " - $currTime :: Attempting to kill digest=$digest runtime=$runTime query=$query\n";
$dbh->do ("kill $dbid");
print STDOUT "$digest\tAutoKll\t$currTime\t$user\@$host\t$state\tid=$dbid\n";
$queryDetails{$detailsKey}{'tokill'} = int(12500000 / $interval);
}
}
} else {
$queryDetails{$detailsKey}{'query'} = $query;
$queryDetails{$detailsKey}{'time'} = gettimeofday();
$queryDetails{$detailsKey}{'state'} = $state;
$queryDetails{$detailsKey}{'user'} = $user;
$queryDetails{$detailsKey}{'host'} = $host;
$queryDetails{$detailsKey}{'dbid'} = $dbid;
$queryDetails{$detailsKey}{'digest'} = $digest;
$queryDetails{$detailsKey}{'tokill'} = 0;
$queryDetails{$detailsKey}{'uSeconds'} = 0;
# get the info about the connection
unless ($connectionDetails{$detailsKey}) { $connectionDetails{$detailsKey} = gettimeofday(); }
## we need to figure out where we can do this - can't do it at query
## end, because the connection doesn't end there. :( - need to do something
## like the grep below on the donequery bit, but for all connections, not
## just connections doing something.
#delete $connectionDetails{$queryKey};
print STDOUT "$digest\tStart\t$currTime\t$user\@$host\t$state\tid=$dbid\n";
if (length($query) > $queryBytes) {
$queryToPrint = substr($query,0,$queryBytes/2) . "...[snip]..." . substr($query,0 - $queryBytes/2);
} else {
$queryToPrint = $query;
}
print SQLLOGFILE "$digest\t$queryToPrint\n";
}
}
# look through queries checking for done ones
for my $queryKey (keys %queryDetails) {
$digest = $queryDetails{$queryKey}{'digest'};
my $user = $queryDetails{$queryKey}{'user'};
my $host = $queryDetails{$queryKey}{'host'};
my $state = $queryDetails{$queryKey}{'state'};
# this is a done query -- write out stats about it
if (! scalar grep ( { $queryDetails{$queryKey}{'query'} eq $_ } @this_dbid)) {
$currTime = strftime ('%Y-%m-%d %H:%M:%S', localtime);
printf STDOUT "$digest\tFinish\t$currTime\t$user\@$host\t$state\tid="
. $queryDetails{$queryKey}{'dbid'} . "\t"
. "%3.3f sec\n", gettimeofday() - $queryDetails{$queryKey}{'time'};
delete $queryDetails{$queryKey};
}
}
usleep $interval;
}
sub doRotate {
my $fileName = shift;
my $moveTo;
my $idx;
print " - $fileName\n";
if (-e "$logDir/$fileName") {
foreach $idx (5,4,3,2,1,0) {
if (-e "$logDir/$fileName.$idx") {
$moveTo = $idx + 1;
if (-e "$logDir/$fileName.$moveTo") { unlink "$logDir/$fileName.$moveTo"; }
rename ("$logDir/$fileName.$idx", "$logDir/$fileName.$moveTo");
}
}
rename ("$logDir/$fileName", "$logDir/$fileName.0");
}
}
sub connectDb {
print STDERR " - $currTime :: Attempting to connect to database $dbMachine\n";
$dbh = DBI->connect("dbi:mysql:information_schema:$dbMachine", "$dbUser", "$dbPass", { RaiseError => 0});
# we're going to run this command over and over millions of times
eval { $sth = $dbh->prepare("show full processlist"); };
while ($@) {
# give the database a little breathing room whilst we try to reconnect
# forever
print STDERR " - $currTime :: Sleeping 5 seconds before attempting another reconnect\n";
sleep 5;
$dbh = DBI->connect("dbi:mysql:information_schema:$dbMachine", "$dbUser", "$dbPass", { RaiseError => 0});
# we're going to run this command over and over millions of times
eval { $sth = $dbh->prepare("show full processlist"); };
}
}
sub doArgs {
print "\n";
print "usage: $0 {--daemon|--terminal} [--help] [--rotate]\n";
print " [--killlong=N] [--killvolatile] [--killconn] [--host=host[:port]] [--license]\n";
print "Required argument:\n";
print " --daemon run as a daemon, logging into $logDir\n";
print " --querybytes=N log at most N bytes of a given query, default=1024\n";
print " --terminal run in terminal, STDOUT is statistics, STDERR is queries\n";
print "Optional arguments:\n";
print " --host=h[:p] connect to a specific host and (optional) :port\n";
print " --killlong=N kill >N-second-running things ('thing'==query by default, but can be connection)\n";
print " --killvolatile also kill volatile (insert/update/delete) long-running queries\n";
print " --killconn kill long-connected connections, not long-running queries\n";
print " --rotate do stupid (but effective) rotation of logs in $logDir\n";
print " --license how is this program licensed?\n";
print " --help get help\n";
print "\n";
print "By default, when killing long-running queries, only SELECTs will be killed.\n";
print "It can be dangerous to kill volatile queries, since many apps don't expect\n";
print "that to happen. If the query has 'allowtorun=N' (N is a number) in a comment,\n";
print "it can run N seconds before being killed. When run in --daemon mode, will log\n";
print "output to logfiles. Run with --rotate periodically if you don't use\n";
print "log rotation software on the log files.\n";
}
sub doLicense {
my $licenseText;
$licenseText = "\n"
. qq{ Copyright(c)2005 by Tim Ellis, all rights reserved except as laid out by the \n }
. qq{ license below. \n }
. qq{ \n }
. qq{ GNU GENERAL PUBLIC LICENSE \n }
. qq{ Version 2, June 1991 \n }
. qq{ \n }
. qq{ Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, \n }
. qq{ Fifth Floor, Boston, MA 02110-1301, USA \n }
. qq{ \n }
. qq{ Everyone is permitted to copy and distribute verbatim copies of this license \n }
. qq{ document, but changing it is not allowed. \n }
. qq{ \n }
. qq{ Preamble \n }
. qq{ \n }
. qq{ The licenses for most software are designed to take away your freedom to share \n }
. qq{ and change it. By contrast, the GNU General Public License is intended to \n }
. qq{ guarantee your freedom to share and change free software--to make sure the \n }
. qq{ software is free for all its users. This General Public License applies to most \n }
. qq{ of the Free Software Foundation's software and to any other program whose \n }
. qq{ authors commit to using it. (Some other Free Software Foundation software is \n }
. qq{ covered by the GNU Lesser General Public License instead.) You can apply it to \n }
. qq{ your programs, too. \n }
. qq{ \n }
. qq{ When we speak of free software, we are referring to freedom, not price. Our \n }
. qq{ General Public Licenses are designed to make sure that you have the freedom to \n }
. qq{ distribute copies of free software (and charge for this service if you wish), \n }
. qq{ that you receive source code or can get it if you want it, that you can change \n }
. qq{ the software or use pieces of it in new free programs; and that you know you \n }
. qq{ can do these things. \n }
. qq{ \n }
. qq{ To protect your rights, we need to make restrictions that forbid anyone to deny \n }
. qq{ you these rights or to ask you to surrender the rights. These restrictions \n }
. qq{ translate to certain responsibilities for you if you distribute copies of the \n }
. qq{ software, or if you modify it. \n }
. qq{ \n }
. qq{ For example, if you distribute copies of such a program, whether gratis or for \n }
. qq{ a fee, you must give the recipients all the rights that you have. You must make \n }
. qq{ sure that they, too, receive or can get the source code. And you must show them \n }
. qq{ these terms so they know their rights. \n }
. qq{ \n }
. qq{ We protect your rights with two steps: (1) copyright the software, and (2) \n }
. qq{ offer you this license which gives you legal permission to copy, distribute \n }
. qq{ and/or modify the software. \n }
. qq{ \n }
. qq{ Also, for each author's protection and ours, we want to make certain that \n }
. qq{ everyone understands that there is no warranty for this free software. If the \n }
. qq{ software is modified by someone else and passed on, we want its recipients to \n }
. qq{ know that what they have is not the original, so that any problems introduced \n }
. qq{ by others will not reflect on the original authors' reputations. \n }
. qq{ \n }
. qq{ Finally, any free program is threatened constantly by software patents. We wish \n }
. qq{ to avoid the danger that redistributors of a free program will individually \n }
. qq{ obtain patent licenses, in effect making the program proprietary. To prevent \n }
. qq{ this, we have made it clear that any patent must be licensed for everyone's \n }
. qq{ free use or not licensed at all. \n }
. qq{ \n }
. qq{ The precise terms and conditions for copying, distribution and modification \n }
. qq{ follow. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION \n }
. qq{ \n }
. qq{ 0. This License applies to any program or other work which contains a notice \n }
. qq{ placed by the copyright holder saying it may be distributed under the terms of \n }
. qq{ this General Public License. The "Program", below, refers to any such program \n }
. qq{ or work, and a "work based on the Program" means either the Program or any \n }
. qq{ derivative work under copyright law: that is to say, a work containing the \n }
. qq{ Program or a portion of it, either verbatim or with modifications and/or \n }
. qq{ translated into another language. (Hereinafter, translation is included without \n }
. qq{ limitation in the term "modification".) Each licensee is addressed as "you". \n }
. qq{ \n }
. qq{ Activities other than copying, distribution and modification are not covered by \n }
. qq{ this License; they are outside its scope. The act of running the Program is not \n }
. qq{ restricted, and the output from the Program is covered only if its contents \n }
. qq{ constitute a work based on the Program (independent of having been made by \n }
. qq{ running the Program). Whether that is true depends on what the Program does. \n }
. qq{ \n }
. qq{ 1. You may copy and distribute verbatim copies of the Program's source code as \n }
. qq{ you receive it, in any medium, provided that you conspicuously and \n }
. qq{ appropriately publish on each copy an appropriate copyright notice and \n }
. qq{ disclaimer of warranty; keep intact all the notices that refer to this License \n }
. qq{ and to the absence of any warranty; and give any other recipients of the \n }
. qq{ Program a copy of this License along with the Program. \n }
. qq{ \n }
. qq{ You may charge a fee for the physical act of transferring a copy, and you may \n }
. qq{ at your option offer warranty protection in exchange for a fee. \n }
. qq{ \n }
. qq{ 2. You may modify your copy or copies of the Program or any portion of it, thus \n }
. qq{ forming a work based on the Program, and copy and distribute such modifications \n }
. qq{ or work under the terms of Section 1 above, provided that you also meet all of \n }
. qq{ these conditions: \n }
. qq{ \n }
. qq{ a) You must cause the modified files to carry prominent notices stating that \n }
. qq{ you changed the files and the date of any change. \n }
. qq{ \n }
. qq{ b) You must cause any work that you distribute or publish, that in whole or in \n }
. qq{ part contains or is derived from the Program or any part thereof, to be \n }
. qq{ licensed as a whole at no charge to all third parties under the terms of this \n }
. qq{ License. \n }
. qq{ \n }
. qq{ c) If the modified program normally reads commands interactively when run, you \n }
. qq{ must cause it, when started running for such interactive use in the most \n }
. qq{ ordinary way, to print or display an announcement including an appropriate \n }
. qq{ copyright notice and a notice that there is no warranty (or else, saying that \n }
. qq{ you provide a warranty) and that users may redistribute the program under these \n }
. qq{ conditions, and telling the user how to view a copy of this License. \n }
. qq{ (Exception: if the Program itself is interactive but does not normally print \n }
. qq{ such an announcement, your work based on the Program is not required to print \n }
. qq{ an announcement.) \n }
. qq{ \n }
. qq{ These requirements apply to the modified work as a whole. If identifiable \n }
. qq{ sections of that work are not derived from the Program, and can be reasonably \n }
. qq{ considered independent and separate works in themselves, then this License, and \n }
. qq{ its terms, do not apply to those sections when you distribute them as separate \n }
. qq{ works. But when you distribute the same sections as part of a whole which is a \n }
. qq{ work based on the Program, the distribution of the whole must be on the terms \n }
. qq{ of this License, whose permissions for other licensees extend to the entire \n }
. qq{ whole, and thus to each and every part regardless of who wrote it. \n }
. qq{ \n }
. qq{ Thus, it is not the intent of this section to claim rights or contest your \n }
. qq{ rights to work written entirely by you; rather, the intent is to exercise the \n }
. qq{ right to control the distribution of derivative or collective works based on \n }
. qq{ the Program. \n }
. qq{ \n }
. qq{ In addition, mere aggregation of another work not based on the Program with the \n }
. qq{ Program (or with a work based on the Program) on a volume of a storage or \n }
. qq{ distribution medium does not bring the other work under the scope of this \n }
. qq{ License. \n }
. qq{ \n }
. qq{ 3. You may copy and distribute the Program (or a work based on it, under \n }
. qq{ Section 2) in object code or executable form under the terms of Sections 1 and \n }
. qq{ 2 above provided that you also do one of the following: \n }
. qq{ \n }
. qq{ a) Accompany it with the complete corresponding machine-readable source code, \n }
. qq{ which must be distributed under the terms of Sections 1 and 2 above on a medium \n }
. qq{ customarily used for software interchange; or, \n }
. qq{ \n }
. qq{ b) Accompany it with a written offer, valid for at least three years, to give \n }
. qq{ any third party, for a charge no more than your cost of physically performing \n }
. qq{ source distribution, a complete machine-readable copy of the corresponding \n }
. qq{ source code, to be distributed under the terms of Sections 1 and 2 above on a \n }
. qq{ medium customarily used for software interchange; or, \n }
. qq{ \n }
. qq{ c) Accompany it with the information you received as to the offer to distribute \n }
. qq{ corresponding source code. (This alternative is allowed only for noncommercial \n }
. qq{ distribution and only if you received the program in object code or executable \n }
. qq{ form with such an offer, in accord with Subsection b above.) \n }
. qq{ \n }
. qq{ The source code for a work means the preferred form of the work for making \n }
. qq{ modifications to it. For an executable work, complete source code means all the \n }
. qq{ source code for all modules it contains, plus any associated interface \n }
. qq{ definition files, plus the scripts used to control compilation and installation \n }
. qq{ of the executable. However, as a special exception, the source code distributed \n }
. qq{ need not include anything that is normally distributed (in either source or \n }
. qq{ binary form) with the major components (compiler, kernel, and so on) of the \n }
. qq{ operating system on which the executable runs, unless that component itself \n }
. qq{ accompanies the executable. \n }
. qq{ \n }
. qq{ If distribution of executable or object code is made by offering access to copy \n }
. qq{ from a designated place, then offering equivalent access to copy the source \n }
. qq{ code from the same place counts as distribution of the source code, even though \n }
. qq{ third parties are not compelled to copy the source along with the object code. \n }
. qq{ \n }
. qq{ 4. You may not copy, modify, sublicense, or distribute the Program except as \n }
. qq{ expressly provided under this License. Any attempt otherwise to copy, modify, \n }
. qq{ sublicense or distribute the Program is void, and will automatically terminate \n }
. qq{ your rights under this License. However, parties who have received copies, or \n }
. qq{ rights, from you under this License will not have their licenses terminated so \n }
. qq{ long as such parties remain in full compliance. \n }
. qq{ \n }
. qq{ 5. You are not required to accept this License, since you have not signed it. \n }
. qq{ However, nothing else grants you permission to modify or distribute the Program \n }
. qq{ or its derivative works. These actions are prohibited by law if you do not \n }
. qq{ accept this License. Therefore, by modifying or distributing the Program (or \n }
. qq{ any work based on the Program), you indicate your acceptance of this License to \n }
. qq{ do so, and all its terms and conditions for copying, distributing or modifying \n }
. qq{ the Program or works based on it. \n }
. qq{ \n }
. qq{ 6. Each time you redistribute the Program (or any work based on the Program), \n }
. qq{ the recipient automatically receives a license from the original licensor to \n }
. qq{ copy, distribute or modify the Program subject to these terms and conditions. \n }
. qq{ You may not impose any further restrictions on the recipients' exercise of the \n }
. qq{ rights granted herein. You are not responsible for enforcing compliance by \n }
. qq{ third parties to this License. \n }
. qq{ \n }
. qq{ 7. If, as a consequence of a court judgment or allegation of patent \n }
. qq{ infringement or for any other reason (not limited to patent issues), conditions \n }
. qq{ are imposed on you (whether by court order, agreement or otherwise) that \n }
. qq{ contradict the conditions of this License, they do not excuse you from the \n }
. qq{ conditions of this License. If you cannot distribute so as to satisfy \n }
. qq{ simultaneously your obligations under this License and any other pertinent \n }
. qq{ obligations, then as a consequence you may not distribute the Program at all. \n }
. qq{ For example, if a patent license would not permit royalty-free redistribution \n }
. qq{ of the Program by all those who receive copies directly or indirectly through \n }
. qq{ you, then the only way you could satisfy both it and this License would be to \n }
. qq{ refrain entirely from distribution of the Program. \n }
. qq{ \n }
. qq{ If any portion of this section is held invalid or unenforceable under any \n }
. qq{ particular circumstance, the balance of the section is intended to apply and \n }
. qq{ the section as a whole is intended to apply in other circumstances. \n }
. qq{ \n }
. qq{ It is not the purpose of this section to induce you to infringe any patents or \n }
. qq{ other property right claims or to contest validity of any such claims; this \n }
. qq{ section has the sole purpose of protecting the integrity of the free software \n }
. qq{ distribution system, which is implemented by public license practices. Many \n }
. qq{ people have made generous contributions to the wide range of software \n }
. qq{ distributed through that system in reliance on consistent application of that \n }
. qq{ system; it is up to the author/donor to decide if he or she is willing to \n }
. qq{ distribute software through any other system and a licensee cannot impose that \n }
. qq{ choice. \n }
. qq{ \n }
. qq{ This section is intended to make thoroughly clear what is believed to be a \n }
. qq{ consequence of the rest of this License. \n }
. qq{ \n }
. qq{ 8. If the distribution and/or use of the Program is restricted in certain \n }
. qq{ countries either by patents or by copyrighted interfaces, the original \n }
. qq{ copyright holder who places the Program under this License may add an explicit \n }
. qq{ geographical distribution limitation excluding those countries, so that \n }
. qq{ distribution is permitted only in or among countries not thus excluded. In such \n }
. qq{ case, this License incorporates the limitation as if written in the body of \n }
. qq{ this License. \n }
. qq{ \n }
. qq{ 9. The Free Software Foundation may publish revised and/or new versions of the \n }
. qq{ General Public License from time to time. Such new versions will be similar in \n }
. qq{ spirit to the present version, but may differ in detail to address new problems \n }
. qq{ or concerns. \n }
. qq{ \n }
. qq{ Each version is given a distinguishing version number. If the Program specifies \n }
. qq{ a version number of this License which applies to it and "any later version", \n }
. qq{ you have the option of following the terms and conditions either of that \n }
. qq{ version or of any later version published by the Free Software Foundation. If \n }
. qq{ the Program does not specify a version number of this License, you may choose \n }
. qq{ any version ever published by the Free Software Foundation. \n }
. qq{ \n }
. qq{ 10. If you wish to incorporate parts of the Program into other free programs \n }
. qq{ whose distribution conditions are different, write to the author to ask for \n }
. qq{ permission. For software which is copyrighted by the Free Software Foundation, \n }
. qq{ write to the Free Software Foundation; we sometimes make exceptions for this. \n }
. qq{ Our decision will be guided by the two goals of preserving the free status of \n }
. qq{ all derivatives of our free software and of promoting the sharing and reuse of \n }
. qq{ software generally. \n }
. qq{ \n }
. qq{ NO WARRANTY \n }
. qq{ \n }
. qq{ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR \n }
. qq{ THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE \n }
. qq{ STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE \n }
. qq{ PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, \n }
. qq{ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND \n }
. qq{ FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND \n }
. qq{ PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU \n }
. qq{ ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. \n }
. qq{ \n }
. qq{ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL \n }
. qq{ ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE \n }
. qq{ PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY \n }
. qq{ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR \n }
. qq{ INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA \n }
. qq{ BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A \n }
. qq{ FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER \n }
. qq{ OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. \n }
;
print $licenseText;
}