#!/usr/bin/python ## # db access spec: http://www.python.org/dev/peps/pep-0249/ ## # ... # md5 stuff import md5 ## >>> import md5 ## >>> mmdd = md5.new("some string or whatever") ## >>> dir(mmdd) ## ['copy', 'digest', 'hexdigest', 'update'] ## >>> mmdd.hexdigest() ## 'c89b653491ae4f5d849954a3fc2091c2' ## del(mmdd) ## # commandline args ## >>> import sys ## >>> sys.argv <--is a list ## # to do this... datestr = "YYYY-MM-DD hh:mm:ss" ## >>> import time ## >>> datestr = time.strftime("%Y-%M-%d %H:%M:%S") ## >>> datestr ## '2007-47-02 20:47:08' # logging locations logDir = "/var/log/dbmon" logFile = "dbmon.log" errFile = "dbmon.err" sqlFile = "dbmon.sql" # 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. interval = 25 * 1000 daemon = 0 killLongRunning = 0 noKillVolatile = 1 killConn = 0 doRotate = 0 from optparse import OptionParser parser = OptionParser() # -h is implied! python is teh r0000l1!11111!eleventy parser.add_option("-d", "--daemon", dest="daemon", action="store_true", default=False, help="daemonise the script") parser.add_option("-t", "--terminal", dest="daemon", action="store_false", default=False, help="run the script in a terminal (STDOUT/STDERR)") parser.add_option("-l", "--license", dest="showlicense", action="store_true", default=False, help="show the license for this script") parser.add_option("-k", "--killlong", dest="killLongRunning", default=0, help="kill long-running queries that go longer than N seconds") parser.add_option("-o", "--killvolatile", dest="noKillVolatile", action="store_false", default=True, help="kill long-running queries are volatile (eg INSERT, UPDATE, DELETE, specifically, non-SELECT)") ## # following is Perl - make it Python ## if (arg =~ /^-+killconn$/) { $killConn = 1; } #... ## if (arg =~ /^-+rotate$/) { $doRotate = 1; } #... if (showlicense): doLicense() exit(0) # we'll overwrite this from time to time currTime = time.strftime('%Y-%m-%d %H:%M:%S') # kill existing monitor import logging logging.info(" - Looking for existing monitor...") # I do this in Perl ALL THE FSCKING TIME import commands pid = commands.getoutput("""ps auxww | grep 'MySQL Query Monitor' | grep -v grep | head -1 | awk '{print $2;}'""") killCount = 0 while (pid and killCount <= 5): logging.info(" + Killing existing monitor...") killout = commands.getoutput("kill -2 %d" % (pid)) usleep(200000) pid = commands.getoutput("""ps auxww | grep 'MySQL Query Monitor' | grep -v grep | head -1 | awk '{print $2;}'""") if (pid): killout = commands.getoutput("kill -9 %d" % (pid)) usleep(50000) pid = commands.getoutput("""ps auxww | grep 'MySQL Query Monitor' | grep -v grep | head -1 | awk '{print $2;}'""") killcount += 1 if (killCount > 4): logging.info(" - Had trouble killing old monitor. Check logs at %s" % logDir exit(255) # rotate out old log info if user requests it if (doRotate): print " - Rotating out log information\n" doRotate("dbmon.err") doRotate("dbmon.sql") doRotate("dbmon.log") # daemonise!!! if (daemon) { logging.info(" - Daemonising...") # make the logdir if it doesn't exist mkdir "$logDir" unless (-d "$logDir") 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 - Statistics Messages will go into $logDir/$logFile at $currTime\n" print STDOUT " - MD5 hash-->SQL mapping is in $logDir/$sqlFile\n" print STDERR "\n - Error Messages will go into $logDir/$errFile at $currTime\n" print SQLLOGFILE "\nmd5-hash\tquery-text\n" exit (0) if (fork()) POSIX::setsid() exit (0) if (fork()) $0 = "$0 killLongRunning=$killLongRunning MySQL Query Monitor - see $logDir/$logFile" } # make database connection dbh sth &connectDb() my %queryDetails my %connectionDetails 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") 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 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 } # kill long-running queries but only attempt this every 5 seconds killFlag = 0 connTime = 0 if (exists $connectionDetails{$detailsKey}) { $connTime = gettimeofday() - $connectionDetails{$detailsKey}; } if (($killLongRunning && ($time >= $killLongRunning) && (($time - $killLongRunning) % 5 == 0)) || ($killLongRunning && ($connTime >= $killLongRunning) && (($connTime - $killLongRunning) % 5 == 0)) ) { # 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+)/) { secondsAllowed = $1 if ($time > $secondsAllowed) { $killFlag = 1 } else { print STDERR " - $currTime :: 'allowtorun=$secondsAllowed' > seconds=$time. Not killing. digest=$digest query=$query\n" } } else { $killFlag = 1 } if ($killFlag) { print STDERR " - $currTime :: Attempting to kill digest=$digest query=$query\n" $dbh->do ("kill $dbid") print STDOUT "$digest\tAutoKll\t$currTime\t$user\@$host\t$state\tid=$dbid\n" } } } 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 # 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 ($daemon) { print SQLLOGFILE "$digest\t$query\n" } else { print STDERR "$digest\t$query\n" } } } # look through queries checking for done ones for queryKey (keys %queryDetails) { $digest = $queryDetails{$queryKey}{'digest'} user = $queryDetails{$queryKey}{'user'} host = $queryDetails{$queryKey}{'host'} 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 { fileName = shift moveTo 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:mysql:$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:mysql:$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"); } } } def doArgs(): print "\n" print "usage: $0 {--daemon|--terminal} [--help] [--rotate]\n" print " [--killlong=N] [--killvolatile] [--killconn] [--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 " --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 { 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 }