#!/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 connect on *.* to dbmon@'127.0.0.1' identified by 'dbmon' # grant super on *.* to dbmon@'127.0.0.1' identified by 'dbmon' 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"; my $daemon; my $killLongRunning = 0; my $noKillVolatile = 1; my $killConn = 0; my $doRotate = 0; my $logDir; my $m_interval; # 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 =~ /^-+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; } if ($arg =~ /^-+interval=(.*)$/) { $m_interval = $1; } } print STDERR " - Host is $dbMachine\n"; # interval # 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. unless(defined $m_interval) { $m_interval = 1000; } my $interval = $m_interval * 1000; # 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, ">$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; 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 { $digest = md5_hex($query); } # 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 $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) { print STDERR " - $currTime :: Not killing volatile query. digest=$digest query=$query\n"; } 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'}) { print STDERR " - $currTime :: Attempting to kill digest=$digest query=$query uSeconds=".$queryDetails{$detailsKey}{'uSeconds'}."\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"; print SQLLOGFILE "$digest\t$query\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]] [--interval=N] [--license]\n"; print "Required argument:\n"; print " --daemon run as a daemon, logging into $logDir\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 " --interval=N how many milliseconds to sleep in our \"show full processlist\" loop\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; }