commit - af20b270334f2eaa1bd7d1f1a0605e2560d37bd0
commit + e9138ee9e0cbcfe685e1f1d84d3d36f7494756b5
blob - /dev/null
blob + d6a93266f748d606b884f9434ff662fe80b9dc21 (mode 644)
--- /dev/null
+++ archive/postgresql-perf-tools/LICENSE
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ 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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
blob - /dev/null
blob + ea848826c8dd0ed21d9bc1b3b4171395371bd27c (mode 644)
--- /dev/null
+++ archive/postgresql-perf-tools/README.md
+## PostgreSQL Performance Monitoring Tools
+
+This package includes three useful scripts aimed to help to pinpoint
+performance issues on systems with PostgreSQL as database backend.
+
+All scritps are written in Python.
+Requirements:
+* Python 2.6+
+* psycopg2 module (http://initd.org/psycopg/)
+
+All scripts require a connect to PostgreSQL database. They take traditional
+set of DB credentials: host address, port (5432 by default), database name,
+database user and password.
+
+Note: you may need to modify pg_hba.conf file to allow the scripts to
+connect to the server. Please refer to official documentaion:
+http://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html
+
+For detailed list of command line options use --help / -h option.
+
+### pg-top
+
+*pg-top.py* uses pg_stat_user_tables system table to get current statistics
+of server activity. The information is represented in 'top'-like screen,
+per table and total for the server, and updated dynamically.
+
+The following data is reported:
+* total number of inserted/updated/deleted rows per sec
+* number of inserted rows per second
+* number of updated rows per second
+* number of deleted rows per second
+* number of rows updated with index update
+* number of index scans per second
+* number of sequential scans per second
+* number of rows per second fetched by seq scans
+* number of processes waiting for lock
+* approximate number of rows in table
+
+### pg-stat
+
+*pg-stat.py* is a command-line tool to get advanced server statistics in
+real-time. The information is represented in tabular form, similar to
+'vmstat' output. By default, new data row is printed each 2 seconds.
+
+The following data is reported:
+* size of database in kilobytes
+* write operations: number of rows inserted/updated/deleted (into user tables)
+* total number of index scans
+* total number of sequential scans
+* percentage of sequential scans
+* total number of live rows fetched by seq scans
+* cache: total number of shmem block hit/miss
+* IO: percent of time spent on waiting to read/write the device (>=9.2)
+* number of processes waiting for lock
+* total number of deadlocks (>= 9.2)
+* number of transactions committed/rolled back
+* total number of 'idle in transaction' processes
+* total number of live processes
+
+### pg-info
+
+*pg-info.py* script gathers static performance-related information
+from the pg_stat_xxx tables and tries to identify potential problem sources.
+
+In particular, the following data is reported:
+* Size of entire database on disk
+* Top tables sorted by size on disk
+* Large tables with missing indexes
+* Less frequently accessed indexes ordered by size
+* Most frequently modified tables
+
+
+## Authors And Contributors
+
+These scripts were created as inhouse tools at Parallels (www.parallels.com),
+by Alexander Andreev (aandreev@odin.com).
+
+Do not hesitate to send your patches, issues and proposals!
+
+## License
+
+Released under [GPLv2 License](LICENSE)
blob - /dev/null
blob + acf31b5d7f6719409b62cb151dc4fa0b35e07264 (mode 755)
--- /dev/null
+++ archive/postgresql-perf-tools/pg-info.py
+#!/usr/bin/python
+
+import os
+import sys
+import time
+
+HAS_PA = True
+oldpath = sys.path
+try:
+ bindir, testscript= os.path.split(sys.argv[0])
+ sys.path.append(os.path.join(bindir, "..", "lib"))
+ import pa_config
+except ImportError:
+ HAS_PA = False
+finally:
+ sys.path = oldpath
+
+import psycopg2
+from optparse import OptionParser, OptionGroup
+import logging
+HAS_SQLPARSE = True
+try:
+ import sqlparse
+except ImportError:
+ HAS_SQLPARSE = False
+
+
+class DB:
+ def __init__(self, host, port, database, user, password):
+ self.host = host
+ self.port = port
+ self.database = database
+ self.user = user
+ self.password = password or ""
+
+ def get_name(self):
+ return self.database
+
+ def __str__(self):
+ return "%s@%s:%s db %s" % (self.user, self.host, self.port, self.database)
+
+ def connect(self):
+ return psycopg2.connect(**(self.__dict__))
+
+ @staticmethod
+ def _execute_fetch(con, query, fetchfn, *args):
+ cur = con.cursor()
+ try:
+ if args is None or not len(args):
+ logging.debug(query)
+ cur.execute(query)
+ else:
+ logging.debug(query % args)
+ cur.execute(query, args)
+ return fetchfn(cur)
+ finally:
+ cur.close()
+
+ @staticmethod
+ def execute_fetchone(con, query, *args):
+ return DB._execute_fetch(con, query, lambda cur : cur.fetchone(), *args)
+
+ @staticmethod
+ def execute_fetchall(con, query, *args):
+ return DB._execute_fetch(con, query, lambda cur : cur.fetchall(), *args)
+
+ @staticmethod
+ def execute_fetchval(con, query, *args):
+ ret = DB.execute_fetchone(con, query, *args)
+ if ret is not None and len(ret) > 0:
+ return ret[0]
+
+
+class PgInfo:
+ def __init__(self, con, db_name, width, lines_limit, print_sqls=False):
+ self.con = con
+ self.db_name = db_name
+ self.w = width
+ self.lines_limit = lines_limit
+ self.print_sqls = print_sqls
+
+ def _printSql(self, q):
+ if self.print_sqls:
+ self._printLine()
+ if HAS_SQLPARSE:
+ print sqlparse.format(q, reindent=True, keyword_case='upper')
+ else:
+ print q
+
+ def _printHeader(self, text):
+ self._printLine("=")
+ print text
+
+ def _printLine(self, char="-"):
+ print char * self.w
+
+ def _printTable(self, fmt, sql_ret):
+ self._printLine()
+ for f in fmt:
+ if f[1] != 0:
+ print f[0].rjust(f[1]).upper(),
+ print
+ line = 0
+ for s in sql_ret:
+ if self.lines_limit and line > self.lines_limit:
+ for n in xrange(0, len(fmt)):
+ if fmt[n][1] == 0:
+ continue
+ print "...".rjust(fmt[n][1]),
+ print
+ break
+ line += 1
+ n = 0
+ for v in s:
+ if fmt[n][1] != 0:
+ if len(fmt[n]) > 2 and fmt[n][2]:
+ try:
+ v = fmt[n][2] % v
+ except:
+ v = "0"
+ else:
+ v = str(v)
+ l = len(v)
+ maxl = fmt[n][1]
+ if l > maxl:
+ v = v[0:maxl-3] + "..."
+ print v.rjust(fmt[n][1]),
+ n += 1
+ print
+ print
+
+ def printPGInfo(self):
+ print DB.execute_fetchval(self.con, "SELECT version()")
+ print
+
+ def printDBSize(self):
+ self._printHeader("All schemas sorted by TOTAL_SZ size on disk")
+ q = """
+ SELECT
+ schema,
+ pg_size_pretty(total) AS total,
+ pg_size_pretty(relation) AS relation,
+ pg_size_pretty(indexes) AS indexes,
+ case when total > 0
+ THEN
+ indexes / total
+ ELSE
+ 0
+ END
+ FROM
+ (
+ SELECT
+ schema,
+ sum(pg_total_relation_size(qual_table))::bigint AS total,
+ sum(pg_relation_size(qual_table))::bigint AS relation,
+ sum(pg_indexes_size(qual_table))::bigint AS indexes
+ FROM
+ (
+ SELECT
+ schemaname AS schema,
+ tablename AS table,
+ ('"'||schemaname||'"."'||tablename||'"')::regclass AS qual_table
+ FROM
+ pg_tables
+ WHERE
+ schemaname NOT LIKE 'pg_%'
+ ) s
+ GROUP BY schema
+ ORDER BY total DESC
+ ) s"""
+ self._printSql(q)
+ ret = DB.execute_fetchall(self.con, q)
+ fmt = [('schema', 32), ('*total_sz', 11), ('data_sz', 11), ('index_sz', 11), ('index_sz%', 9, "%.0f%%")]
+ self._printTable(fmt, ret)
+
+ def printTablesSize(self):
+ self._printHeader("All tables sorted by TOTAL_SZ size on disk")
+ q = """
+ SELECT
+ relname,
+ pg_total_relation_size(C.oid) AS total_bytes,
+ pg_size_pretty(pg_total_relation_size(C.oid)) AS "total",
+ pg_size_pretty(pg_relation_size(C.oid)) AS "data_sz",
+ pg_size_pretty(pg_indexes_size(C.oid)) AS "index_sz",
+ case when pg_indexes_size(C.oid) > 0
+ THEN
+ 100 * pg_indexes_size(C.oid) / pg_total_relation_size(C.oid)
+ ELSE
+ 0
+ END,
+ reltuples,
+ columns,
+ indexes,
+ case when columns > 0
+ THEN
+ 100 * indexes / columns
+ ELSE
+ 0
+ END
+ FROM
+ pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
+ LEFT JOIN (
+ SELECT
+ table_name, count(*) AS columns
+ FROM
+ information_schema.columns
+ GROUP BY
+ table_name
+ ) S ON S.table_name = C.relname
+ LEFT JOIN (
+ SELECT
+ tablename, count(tablename) AS indexes
+ FROM
+ pg_indexes
+ GROUP BY
+ tablename
+ ) I ON (I.tablename = C.relname)
+ WHERE
+ nspname NOT IN ('pg_catalog', 'information_schema') AND C.relkind = 'r'
+ ORDER BY
+ total_bytes DESC
+ """
+ self._printSql(q)
+ ret = DB.execute_fetchall(self.con, q)
+ fmt = [('table', 32),
+ ('total_bytes', 0),
+ ('*total_sz', 11), ('data_sz', 11), ('index_sz', 11), ('index_sz%', 9, "%d%%"),
+ ("rows", 11, "%d"), ("columns", 7, "%d"), ("indexes", 7, "%d"), ("idx%", 4, "%d")]
+
+ self._printTable(fmt, ret)
+ print " HINT: The bigger is table TOTAL_SZ the slower are all operations on this table\n"
+
+ def printMissingIndexes(self, pg_rel_size_threshold):
+ if pg_rel_size_threshold:
+ self._printHeader("Tables with size > %dKB and missing indexes (lots of sequential scans)" %
+ (pg_rel_size_threshold / 1024))
+ else:
+ self._printHeader("All tables with missing indexes")
+
+ q = """
+ SELECT
+ relname, seq_scan-idx_scan AS too_much_seq,
+ case when seq_scan-idx_scan > 0
+ THEN
+ 'Missing Index?'
+ ELSE
+ 'OK'
+ END,
+ pg_relation_size(relname::regclass)
+ AS rel_size, seq_scan, idx_scan
+ FROM
+ pg_stat_all_tables
+ WHERE
+ schemaname='public' AND pg_relation_size(relname::regclass) >= %d
+ ORDER BY too_much_seq DESC""" % pg_rel_size_threshold
+
+ self._printSql(q)
+ ret = DB.execute_fetchall(self.con, q)
+ fmt = [('table', 32), ('*too_much_seq', 14), ('case', 15), ('rel_size', 10),
+ ('seq_scan', 10), ('idx_scan', 10)]
+ self._printTable(fmt, ret)
+ print \
+ " HINT: The more TOO_MUCH_SCAN value the more frequently sequential scan was performed on the\n" + \
+ " given table\n"
+
+ def printDeadIndexes(self):
+ self._printHeader("Less frequently accessed indexes ordered by IDX_SIZE")
+ q = """
+ SELECT
+ relid::regclass AS table,
+ indexrelid::regclass AS index,
+ pg_size_pretty(pg_relation_size(indexrelid::regclass)) AS index_size,
+ pg_relation_size(indexrelid::regclass) AS index_size_bytes,
+ idx_scan,
+ idx_tup_read,
+ idx_tup_fetch
+ FROM
+ pg_stat_user_indexes
+ JOIN pg_index USING (indexrelid)
+ WHERE
+ indisunique IS FALSE
+ ORDER BY idx_scan ASC, index_size_bytes DESC
+ """
+ self._printSql(q)
+ ret = DB.execute_fetchall(self.con, q)
+
+ fmt = [('table', 32), ('index', 40), ('*idx_size', 10), ('idx size bytes', 0),
+ ('*idx_scan', 9), ('tup_read', 9), ('tup_fetch', 9)]
+ self._printTable(fmt, ret)
+
+ print \
+ " HINT: The more IDX_SIZE value the more size is occupied for given index, if IDX_SCAN == 0 then\n" + \
+ " this index is unused and can be just removed to increase INSERT/UPDATE performance\n"
+
+ def printMostWritableTables(self):
+ self._printHeader("Most frequently modified tables")
+ q = """
+ SELECT
+ relname AS TABLE,
+ pg_size_pretty( pg_relation_size(relid) ) AS tsize,
+ n_tup_upd + n_tup_ins + n_tup_del AS WRITE,
+ seq_scan + idx_scan AS READ,
+ case when seq_scan + idx_scan > 0
+ THEN
+ 100 * (n_tup_upd + n_tup_ins + n_tup_del) / (n_tup_upd + n_tup_ins + n_tup_del + seq_scan + idx_scan)
+ ELSE
+ 0
+ END,
+ n_tup_ins AS INS,
+ n_tup_upd AS UPD,
+ n_tup_del AS DEL
+ FROM
+ pg_stat_user_tables
+ ORDER BY
+ ( n_tup_upd + n_tup_ins + n_tup_del ) DESC
+ """
+ self._printSql(q)
+ ret = DB.execute_fetchall(self.con, q)
+
+ fmt = [('table', 32), ('tsize', 10), ('*writes', 11), ('reads', 11),
+ ('write%', 10, "%.0f%%"), ('ins', 10), ('upd', 10), ('del', 10)]
+ self._printTable(fmt, ret)
+ print " HINT: Tables with significant amount of WRITEs can reveal bad application design\n"
+
+def main():
+ test_description = "%prog [options]"
+
+ p = OptionParser(test_description)
+ p.add_option("-v", "--verbose", action="store_true", help="enable verbose mode")
+ p.add_option("-s", "--sql", action="store_true", help="print SQLs that were used to obtain the stats")
+ p.add_option("-l", "--lines", type=int, default=20, help="num of lines in output (default %default)")
+ p.add_option("", "--min-tab-size", type=int, default=(32*1024),
+ help="min size of tables when analyze missing indexes (default %default)")
+
+ defdb = ""
+ defusr = "postgres"
+ if HAS_PA:
+ g = OptionGroup(p, "If you have pa.conf")
+ g.add_option("-c", "--config", type="string", default=None, help="PA config file [default: %default]")
+ g.add_option("", "--pba", action="store_true", help = "connect to PBA (POA is default)")
+ p.add_option_group(g)
+ defdb = "plesk"
+ defusr = "plesk"
+
+ g = OptionGroup(p, "If you don't have pa.conf" if HAS_PA else "Database credentials")
+ g.add_option("", "--db-host", type="string", help="database hostname/IP")
+ g.add_option("", "--db-port", type="string", default=5432, help="database port")
+ g.add_option("", "--db-name", type="string", default=defdb, help="database name [default: %default]")
+ g.add_option("", "--db-user", type="string", default=defusr, help="database username [default: %default]")
+ g.add_option("", "--db-pass", type="string", default="", help="database password")
+ p.add_option_group(g)
+
+ opts, args = p.parse_args()
+ loglevel = logging.DEBUG if opts.verbose else logging.WARNING
+ logging.basicConfig(level=loglevel, format="%(asctime)s - %(module)s - %(levelname)s - %(message)s")
+
+ if HAS_PA:
+ if not opts.config and not opts.db_host:
+ p.error("either -c or --db-host option must be provided")
+ else:
+ if not opts.db_host:
+ p.error("--db-host option must be provided")
+
+ if HAS_PA and opts.config:
+ pa_config.init(opts.config)
+ if opts.pba:
+ b = pa_config.get().pba_db
+ else:
+ b = pa_config.get().poa_db
+
+ opts.db_vendor = b.db_vendor
+ opts.db_host = b.ip
+ opts.db_port = b.db_port
+ opts.db_name = b.db_name
+ opts.db_user = b.db_user
+ opts.db_pass = b.db_pass
+
+ db = DB(opts.db_host, opts.db_port, opts.db_name, opts.db_user, opts.db_pass)
+ print "Connecting to %s ..." % str(db)
+ con = db.connect()
+ pi = PgInfo(con, opts.db_name, 110, opts.lines, opts.sql)
+
+ pi.printPGInfo()
+ pi.printDBSize()
+ pi.printTablesSize()
+ pi.printMissingIndexes(opts.min_tab_size)
+ pi.printDeadIndexes()
+ pi.printMostWritableTables()
+
+if __name__ == "__main__":
+ main()
blob - /dev/null
blob + 8e7557d591b88d89dba74b027e3c42051c208658 (mode 755)
--- /dev/null
+++ archive/postgresql-perf-tools/pg-stat.py
+#!/usr/bin/python
+
+import os
+import sys
+import time
+
+HAS_PA = True
+oldpath = sys.path
+try:
+ bindir, testscript= os.path.split(sys.argv[0])
+ sys.path.append(os.path.join(bindir, "..", "lib"))
+ import pa_config
+except ImportError:
+ HAS_PA = False
+finally:
+ sys.path = oldpath
+
+import psycopg2
+from optparse import OptionParser, OptionGroup
+import logging
+import inspect
+
+
+class DB:
+ def __init__(self, host, port, database, user, password):
+ self.host = host
+ self.port = port
+ self.database = database
+ self.user = user
+ self.password = password or ""
+
+ def get_name(self):
+ return self.database
+
+ def __str__(self):
+ return "%s@%s:%s db %s" % (self.user, self.host, self.port, self.database)
+
+ def connect(self):
+ return psycopg2.connect(**(self.__dict__))
+
+ @staticmethod
+ def _execute_fetch(con, query, fetchfn, *args):
+ cur = con.cursor()
+ try:
+ if args is None or not len(args):
+ logging.debug(query)
+ cur.execute(query)
+ else:
+ logging.debug(query % args)
+ cur.execute(query, args)
+ return fetchfn(cur)
+ finally:
+ cur.close()
+
+ @staticmethod
+ def execute_fetchone(con, query, *args):
+ return DB._execute_fetch(con, query, lambda cur : cur.fetchone(), *args)
+
+ @staticmethod
+ def execute_fetchall(con, query, *args):
+ return DB._execute_fetch(con, query, lambda cur : cur.fetchall(), *args)
+
+ @staticmethod
+ def execute_fetchval(con, query, *args):
+ ret = DB.execute_fetchone(con, query, *args)
+ if ret is not None and len(ret) > 0:
+ return ret[0]
+
+
+class PgVersion:
+ def __init__(self, con):
+ self.str = DB.execute_fetchval(con, "SELECT version()")
+ self.maj = DB.execute_fetchval(con, "SELECT substring(version() from $$(\d+)\.\d+\.\d+$$)::smallint;")
+ self.min = DB.execute_fetchval(con, "SELECT substring(version() from $$\d+\.(\d+)\.\d+$$)::smallint;")
+
+ def ge(self, maj, min):
+ return self.maj > maj or (self.maj >= maj and self.min >= min)
+
+
+opts = None
+con = None
+
+class PgStatStore:
+ def __init__(self, table=None, cols=None):
+ self.table = table
+ self.cols = cols
+
+ def update(self):
+ query = ["SUM(%s)" % c for c in self.cols]
+
+ ret = DB.execute_fetchone(con, "SELECT %s FROM %s" % (", ".join(query), self.table))
+ self.store = {}
+ n = 0
+ for c in self.cols:
+ self.store[c] = ret[n]
+ n += 1
+
+ def append(self, column):
+ self.cols.append(column)
+
+class PgStatStoreBigUserTables(PgStatStore):
+ def __init__(self, cols):
+ self.cols = cols
+
+ def update(self):
+ query = ["SUM(p.%s)" % c for c in self.cols]
+
+ ret = DB.execute_fetchone(con,
+ "SELECT %s FROM pg_stat_user_tables p, pg_class c "
+ "WHERE p.relname = c.relname AND c.reltuples > %d" %
+ (", ".join(query), int(opts.scan_threshold)))
+ self.store = {}
+ n = 0
+ for c in self.cols:
+ self.store[c] = ret[n]
+ n += 1
+
+class PgStatStoreProc(PgStatStore):
+ def update(self):
+ ret = DB.execute_fetchall(con,
+ "SELECT query, COUNT(*) FROM pg_stat_activity " + \
+ "WHERE datname = '%s' GROUP BY query" % opts.db_name)
+
+ self.store = {}
+ for row in ret:
+ if "pg_stat_activity" in row[0]:
+ continue
+ self.store[row[0]] = row[1]
+
+class DbStatCounter:
+ width = 5
+ rate_fmt = None
+ absolute = False
+ def __init__(self, store=None):
+ metric_len = len(self.metric)
+ if not self.absolute:
+ metric_len += 2 # '/s'
+ self.width = max(self.width, len(self.title), metric_len)
+ self.store = store
+
+ self.val_initial = 0
+ self.val = 0
+ self.rate = 0
+ self.time = 0
+
+ def update(self):
+ prev_val = float(self.val)
+ self.update_action()
+ prev_time = self.time
+ self.time = time.time()
+ logging.debug("%s raw val: %d" % (self.title, self.val))
+ self.rate = 0
+ if self.absolute:
+ self.rate = self.val
+ else:
+ if prev_time:
+ dt = self.time - prev_time
+ if dt:
+ self.rate = (float(self.val) - prev_val) / dt
+ if not self.val_initial:
+ self.val_initial = self.val
+ con.commit()
+
+ def update_action(self):
+ # virtual
+ self.val = 0
+
+ def abs(self):
+ if self.absolute:
+ return self.val
+ return self.val - self.val_initial
+
+class pgsDbSize(DbStatCounter):
+ title = "DBSize"
+ metric = "KB"
+ width = 8
+ help = "size of database in kilobytes"
+ def update_action(self, **kwargs):
+ self.val = DB.execute_fetchval(con, "select pg_database_size('%s')" % opts.db_name)
+ self.val /= 1024
+
+class pgsWrIns(DbStatCounter):
+ title = "INS"
+ metric = "rows"
+ help = "number of rows inserted into user tables [pg_stat_user_tables.n_tup_ins]"
+ rate_fmt = "%.1f"
+ def update_action(self):
+ self.val = self.store.store["n_tup_ins"]
+
+class pgsWrUpd(DbStatCounter):
+ title = "UPD"
+ metric = "rows"
+ help = "number of rows updated in the user tables [pg_stat_user_tables.n_tup_upd]"
+ rate_fmt = "%.1f"
+ def update_action(self):
+ self.val = self.store.store["n_tup_upd"]
+
+class pgsWrDel(DbStatCounter):
+ title = "DEL"
+ metric = "rows"
+ help = "number of rows deleted in the user tables [pg_stat_user_tables.n_tup_del]"
+ rate_fmt = "%.1f"
+ def update_action(self):
+ self.val = self.store.store["n_tup_del"]
+
+class pgsScanIdx(DbStatCounter):
+ title = "IDX"
+ metric = "scan"
+ help = "total number of index scans [pg_stat_database.idx_scan]"
+ def update_action(self):
+ self.val = self.store.store["idx_scan"]
+
+class pgsScanSeq(DbStatCounter):
+ title = "SEQ"
+ metric = "scan"
+ help = "total number of sequential scans [pg_stat_database.seq_scan]"
+ def update_action(self):
+ self.val = self.store.store["seq_scan"]
+
+class pgsScanIdxPerc(DbStatCounter):
+ title = "SEQ%"
+ metric = "scan%"
+ help = "percentage of sequential scans [100 * pg_stat_database.seq_scan / (.idx_scan + .seq_scan)]"
+ absolute = True
+ def update_action(self):
+ idx = int(self.store.store['idx_scan'])
+ seq = int(self.store.store['seq_scan'])
+
+ if hasattr(self, 'prev_idx'):
+ d_idx = idx - self.prev_idx
+ if d_idx < 0:
+ d_idx = 0
+ d_seq = seq - self.prev_seq
+ if d_seq < 0:
+ d_seq = 0
+ tot = d_idx + d_seq
+ self.val = ((100 * int(d_seq)) / float(tot)) if tot else 0
+ else:
+ self.val = 0
+ self.prev_idx = idx
+ self.prev_seq = seq
+
+
+class pgsScanSeqRows(DbStatCounter):
+ title = "SEQ_ROWS"
+ metric = "rows"
+ help = "total number of live rows fetched by seq scans [pg_stat_database.seq_tup_read]"
+ rate_fmt = "%.0f"
+ def update_action(self):
+ self.val = self.store.store["seq_tup_read"]
+
+class pgsCacheMiss(DbStatCounter):
+ title = "MISS"
+ metric = "blk"
+ help = "total number of shmem block miss [pg_stat_database.blks_read - pg_stat_database.blks_hit]"
+ def update_action(self):
+ self.val = self.store.store["blks_read"]
+
+class pgsCacheHit(DbStatCounter):
+ title = "HIT"
+ metric = "blk"
+ width = 6
+ help = "total number of shmem block hits [pg_stat_database.blks_hit]"
+ def update_action(self):
+ self.val = self.store.store['blks_hit']
+
+class pgsIoReadWa(DbStatCounter):
+ title = "READWA"
+ metric = "wait%"
+ help = "percent of time spent on IO read's wait [100 * pg_stat_database.blk_read_time / wall_time] (>= 9.2)"
+ def update_action(self):
+ dt = time.time() - self.time
+ wa = self.store.store['blk_read_time']
+ self.val = 100 * wa / dt if dt else 0
+
+class pgsIoWriteWa(DbStatCounter):
+ title = "WRITEWA"
+ metric = "wait%"
+ help = "percent of time spent on IO write's wait [100 * pg_stat_database.blk_write_time / wall_time] (>= 9.2)"
+ def update_action(self):
+ dt = time.time() - self.time
+ wa = self.store.store['blk_write_time']
+ self.val = 100 * wa / dt if dt else 0
+
+class pgsTxnCommit(DbStatCounter):
+ title = "COMMIT"
+ metric = "txn"
+ help = "number of committed transactions [pg_stat_database.xact_commit]"
+ def update_action(self):
+ self.val = self.store.store['xact_commit']
+
+class pgsTxnRollback(DbStatCounter):
+ title = "RLLBCK"
+ metric = "txn"
+ help = "number of rolled back transactions [pg_stat_database.xact_rollback]"
+ def update_action(self):
+ self.val = self.store.store['xact_rollback']
+
+class pgsLockWait(DbStatCounter):
+ width = 5
+ title = "LOCK"
+ metric = "cnt"
+ help = "number of processes waiting for lock [COUNT(*) FROM pg_locks WHERE NOT granted]"
+ rate_fmt = "%d"
+ absolute = True
+ def update_action(self):
+ self.val = DB.execute_fetchval(con, "SELECT COUNT(*) FROM pg_locks WHERE NOT granted")
+
+class pgsDeadlocks(DbStatCounter):
+ title = "DEADLOCK"
+ metric = "cnt"
+ help = "total number of deadlocks [pg_stat_database.deadlocks] (>= 9.2)"
+ def update_action(self):
+ self.val = self.store.store['deadlocks']
+
+class pgsProcsIdletxn(DbStatCounter):
+ width = 5
+ title = "PROC"
+ metric = "idltxn"
+ help = "total number of 'idle in transaction' processes"
+ absolute = True
+ rate_fmt = "%d"
+ def update_action(self):
+ key = "<IDLE> in transaction"
+ self.val = int(self.store.store[key]) if self.store.store.has_key(key) else 0
+
+class pgsProcsLive(DbStatCounter):
+ width = 3
+ title = "PROC"
+ metric = "live"
+ help = "total number of live processes"
+ absolute = True
+ rate_fmt = "%d"
+ def update_action(self):
+ self.val = 0
+ for key, val in self.store.store.items():
+ if key.startswith("<IDLE>"):
+ continue
+ self.val += int(val)
+
+class PgStats:
+ def __init__(self):
+ self.sep = " |"
+ self.hdr_titles = ""
+ self.hdr_metrics = ""
+ self.fmt = ""
+
+ pg_ver = PgVersion(con)
+ print pg_ver.str
+
+ s_db = PgStatStore("pg_stat_database", ["xact_commit", "xact_rollback", "blks_read", "blks_hit"])
+ s_ut = PgStatStore("pg_stat_user_tables", ["n_tup_ins", "n_tup_upd", "n_tup_del"])
+ s_utb = PgStatStoreBigUserTables(["idx_scan", "seq_scan", "seq_tup_read"])
+ s_pr = PgStatStoreProc()
+
+ if pg_ver.ge(9, 2):
+ s_db.append("deadlocks")
+ s_db.append("blk_read_time")
+ s_db.append("blk_write_time")
+
+ self.stores = [s_db, s_ut, s_utb, s_pr]
+
+ self.groups = [
+ ("DataBase", [pgsDbSize()]),
+ ("Write Ops", [pgsWrIns(s_ut), pgsWrUpd(s_ut), pgsWrDel(s_ut)]),
+ ("Scan (tables with >%dK rows)" % (opts.scan_threshold / 1000),
+ [pgsScanIdx(s_utb), pgsScanSeq(s_utb), pgsScanIdxPerc(s_utb), pgsScanSeqRows(s_utb)]),
+ ("CacheRead", [pgsCacheHit(s_db), pgsCacheMiss(s_db)]),
+ ("Locks", [pgsLockWait()] + ([pgsDeadlocks(s_db)] if pg_ver.ge(9, 2) else [])),
+ ("Transactions", [pgsTxnCommit(s_db), pgsTxnRollback(s_db)]),
+ ("Proc", [pgsProcsIdletxn(s_pr), pgsProcsLive(s_pr)]),
+ ]
+
+ if pg_ver.ge(9, 2):
+ self.groups.append(("Disk Wait", [pgsIoReadWa(s_db), pgsIoWriteWa(s_db)]))
+
+ self.init()
+
+ def init(self):
+ self.counters = []
+
+ for group in self.groups:
+ for c in group[1]:
+ self.counters.append(c)
+ self.hdr_metrics += " " + c.title.rjust(c.width)
+ self.fmt += " %%%ds" % c.width
+ self.hdr_titles += " " + group[0].rjust(sum(c.width + 1 for c in group[1]) - 1) + self.sep
+ self.hdr_metrics += self.sep
+ self.fmt += self.sep
+
+ def header(self):
+ print "=" * len(self.hdr_titles)
+ print self.hdr_titles
+ print self.hdr_metrics
+ if opts.abs:
+ metrics = [c.metric for c in self.counters]
+ else:
+ metrics = [c.metric if c.absolute else "%s/s" % c.metric for c in self.counters]
+ print self.fmt % tuple(metrics)
+ print "+" * len(self.hdr_titles)
+
+ def print_row(self):
+ if opts.abs:
+ vals = ["%d" % c.abs() for c in self.counters]
+ else:
+ vals = []
+ for c in self.counters:
+ r = c.val if c.absolute else c.rate
+ fmt = c.rate_fmt if c.rate_fmt else ("%.1f" if r < 100 else "%.0f")
+ vals.append(fmt % r)
+ print self.fmt % tuple(vals)
+
+ def update(self):
+ for s in self.stores:
+ s.update()
+ for c in self.counters:
+ c.update()
+
+def pg_usage():
+ ps = PgStats()
+ ps.header()
+ ps.update()
+ try:
+ i = 0
+ while True:
+ time.sleep(opts.delay)
+ ps.update()
+ ps.print_row()
+ if opts.count:
+ i += 1
+ if opts.count <= i:
+ break
+ except KeyboardInterrupt, e:
+ pass
+
+def main():
+ global opts
+ global con
+
+ test_description = "%prog [options]"
+
+ epilog = "\nCounters description:"
+ for name, obj in inspect.getmembers(sys.modules[__name__]):
+ if inspect.isclass(obj) and issubclass(obj, DbStatCounter) and hasattr(obj, "title"):
+ epilog += "\n%18s - %s" % (obj.title + " (" + obj.metric + ")", obj.help)
+
+ class PgOptParser(OptionParser):
+ def format_epilog(self, formatter):
+ return self.epilog + "\n"
+
+ p = PgOptParser(test_description, epilog=epilog)
+ p.add_option("-v", "--verbose", action="store_true", help="enable verbose mode")
+ p.add_option("-d", "--delay", type=int, default=2, help="delay between database poll (sec)")
+ p.add_option("-n", "--count", type=int, default=0, help="exit after COUNT iterations")
+ p.add_option("-a", "--abs", action="store_true", help="show absolute values, not rates")
+ p.add_option("-r", "--scan-threshold", type=int, default=5000,
+ help="skip tables with fewer rows when collect IDX and SEQ scan stats")
+
+ defdb = ""
+ defusr = "postgres"
+ if HAS_PA:
+ g = OptionGroup(p, "If you have pa.conf")
+ g.add_option("-c", "--config", type="string", default=None, help="PA config file [default: %default]")
+ g.add_option("", "--pba", action="store_true", help = "connect to PBA (POA is default)")
+ p.add_option_group(g)
+ defdb = "plesk"
+ defusr = "plesk"
+
+ g = OptionGroup(p, "If you don't have pa.conf" if HAS_PA else "Database credentials")
+ g.add_option("", "--db-host", type="string", help="database hostname/IP")
+ g.add_option("", "--db-port", type="string", default=5432, help="database port")
+ g.add_option("", "--db-name", type="string", default=defdb, help="database name [default: %default]")
+ g.add_option("", "--db-user", type="string", default=defusr, help="database username [default: %default]")
+ g.add_option("", "--db-pass", type="string", default="", help="database password")
+ p.add_option_group(g)
+
+ opts, args = p.parse_args()
+ loglevel = logging.DEBUG if opts.verbose else logging.WARNING
+ logging.basicConfig(level=loglevel, format="%(asctime)s - %(module)s - %(levelname)s - %(message)s")
+
+ if HAS_PA:
+ if not opts.config and not opts.db_host:
+ p.error("either -c or --db-host option must be provided")
+ else:
+ if not opts.db_host:
+ p.error("--db-host option must be provided")
+
+ if HAS_PA and opts.config:
+ pa_config.init(opts.config)
+ if opts.pba:
+ b = pa_config.get().pba_db
+ else:
+ b = pa_config.get().poa_db
+
+ opts.db_vendor = b.db_vendor
+ opts.db_host = b.ip
+ opts.db_port = b.db_port
+ opts.db_name = b.db_name
+ opts.db_user = b.db_user
+ opts.db_pass = b.db_pass
+
+ db = DB(opts.db_host, opts.db_port, opts.db_name, opts.db_user, opts.db_pass)
+ print "Connecting to %s ..." % str(db)
+ con = db.connect()
+ pg_usage()
+
+if __name__ == "__main__":
+ main()
blob - /dev/null
blob + 65b7591325957779ac91c4e9b70044515c5c8688 (mode 755)
--- /dev/null
+++ archive/postgresql-perf-tools/pg-top.py
+#!/usr/bin/python
+
+import os
+import sys
+import time
+
+HAS_PA = True
+oldpath = sys.path
+try:
+ bindir, testscript= os.path.split(sys.argv[0])
+ sys.path.append(os.path.join(bindir, "..", "lib"))
+ import pa_config
+except ImportError:
+ HAS_PA = False
+finally:
+ sys.path = oldpath
+
+import psycopg2
+from optparse import OptionParser, OptionGroup
+import logging
+import inspect
+
+import threading
+import curses
+import mutex
+import StringIO
+import traceback
+import copy
+
+
+class DB:
+ def __init__(self, host, port, database, user, password):
+ self.host = host
+ self.port = port
+ self.database = database
+ self.user = user
+ self.password = password or ""
+
+ def get_name(self):
+ return self.database
+
+ def __str__(self):
+ return "%s@%s:%s db %s" % (self.user, self.host, self.port, self.database)
+
+ def connect(self):
+ return psycopg2.connect(**(self.__dict__))
+
+ @staticmethod
+ def _execute_fetch(con, query, *args):
+ cur = con.cursor()
+
+ @staticmethod
+ def _execute_fetch(con, query, fetchfn, *args):
+ cur = con.cursor()
+ try:
+ if args is None or not len(args):
+ logging.debug(query)
+ cur.execute(query)
+ else:
+ logging.debug(query % args)
+ cur.execute(query, args)
+ return fetchfn(cur)
+ finally:
+ cur.close()
+
+ @staticmethod
+ def execute_fetchone(con, query, *args):
+ return DB._execute_fetch(con, query, lambda cur : cur.fetchone(), *args)
+
+ @staticmethod
+ def execute_fetchall(con, query, *args):
+ return DB._execute_fetch(con, query, lambda cur : cur.fetchall(), *args)
+
+ @staticmethod
+ def execute_fetchval(con, query, *args):
+ ret = DB.execute_fetchone(con, query, *args)
+ if ret is not None and len(ret) > 0:
+ return ret[0]
+
+
+KEY_LEFT = 68
+KEY_RIGHT = 67
+
+USER_COL_NAME = 0
+USER_COL_WIDTH = 1
+USER_COL_TYPE = 2
+USER_COL_ABS = 3
+USER_COL_METRIC = 4
+USER_COL_SQL_NAME = 5
+USER_COL_HELP = 6
+
+user_cols_def = [
+ # title #width #type #abs #metric #sql_name #help
+ ["Table", 0, "str", True, "", "tablename", "table name"],
+ ["DB", 5, "str", True, "", "dbname", "database"],
+ ["Write", 6, "int", False, "row/s", "writes", "total number of inserted/updated/deleted rows per sec"],
+ ["Ins", 6, "int", False, "row/s", "n_tup_ins", "number of inserted rows per second"],
+ ["Upd", 6, "int", False, "row/s", "n_tup_upd", "number of updated rows per second"],
+ ["Del", 6, "int", False, "row/s", "n_tup_del", "number of deleted rows per second"],
+ ["UpdIdx", 8, "float", False, "row/s", "n_tup_idx_upd", "number of rows updated with index update"],
+ ["IdxScan", 9, "int", False, "scan/s", "idx_scan", "number of index scans per second"],
+ ["SeqScan", 9, "int", False, "scan/s", "seq_scan", "number of sequential scans per second"],
+ ["SeqRows", 9, "int", False, "row/s", "seq_tup_read", "number of rows per second fetched by seq scans"],
+ ["Locks", 6, "int", True, "count", "locks", "number of processes waiting for lock"],
+ ["Reltuples", 10, "int", True, "count", "reltuples", "approximate number of rows in table"]
+]
+
+user_cols_select_query = """
+SELECT
+ %s
+FROM
+ pg_stat_user_tables U
+
+ LEFT JOIN (
+ SELECT
+ case WHEN schemaname = 'public'
+ THEN
+ relname
+ ELSE
+ schemaname || '.' || relname
+ END tablename,
+ schemaname AS T_schemaname,
+ relname AS T_relname,
+ current_database() dbname,
+ (n_tup_ins + n_tup_upd + n_tup_del) writes,
+ (n_tup_upd - n_tup_hot_upd) n_tup_idx_upd
+ FROM
+ pg_stat_user_tables
+ ) T ON (T_relname = relname AND T_schemaname = U.schemaname)
+
+ LEFT JOIN (
+ SELECT
+ relname AS L_relname,
+ nspname AS L_schemaname,
+ reltuples,
+ case WHEN L_locks > 0
+ THEN
+ L_locks
+ ELSE
+ 0
+ END locks
+ FROM
+ pg_class C
+
+ LEFT JOIN (
+ SELECT
+ relation,
+ COUNT(*) L_locks
+ FROM
+ pg_locks
+ WHERE
+ NOT granted
+ GROUP BY
+ relation
+ ) L ON L.relation = C.oid
+
+ LEFT JOIN pg_namespace n ON n.oid = C.relnamespace
+
+ ) L ON (L_relname = relname AND L_schemaname = U.schemaname)
+"""
+
+user_cols_select_query_for_schema = user_cols_select_query + "WHERE U.schemaname = '%s'"
+
+
+class PgTop:
+ def __init__(self):
+ self.scr = None
+ self.opts = None
+ self.con = []
+
+ self.paused = 0
+ self.terminate = False
+
+ self.mutex = threading.Lock()
+ self.prev_time = 0
+
+ if sys.stderr.isatty():
+ sys.stderr = StringIO.StringIO()
+
+ self.init_user_cols()
+
+ def init_user_cols(self):
+ self.user_cols_sorted = 0
+ self.user_cols_hash = {}
+ self.user_cols_data_prev = {}
+ self.user_cols_meta = []
+ self.user_cols_view_data = []
+ self.user_cols_view_ctime = time.ctime()
+
+ for col in user_cols_def:
+
+ # hide dB if there is only one DB
+ if col[USER_COL_NAME].lower() == "db" and len(self.con) == 1:
+ continue
+
+ self.user_cols_meta.append(col)
+
+ for n in xrange(0, len(self.user_cols_meta)):
+ self.user_cols_hash[self.user_cols_meta[n][USER_COL_NAME]] = n
+ if self.opts and self.user_cols_meta[n][USER_COL_NAME] == self.opts.sort:
+ self.user_cols_sorted = n
+
+ def init(self, scr, con, opts):
+ self.scr = scr
+ self.con = con
+ self.opts = opts
+ self.init_user_cols()
+
+ def fetch_user_cols(self):
+ cols = ", ".join([c[USER_COL_SQL_NAME] for c in self.user_cols_meta])
+ data = []
+ for name, con in self.con.items():
+ if self.opts.schema:
+ data += DB.execute_fetchall(con, user_cols_select_query_for_schema % (cols, self.opts.schema))
+ else:
+ data += DB.execute_fetchall(con, user_cols_select_query % cols)
+ con.commit()
+ return data
+
+ def update_user_cols_view(self):
+ self.user_cols_view_ctime = time.ctime()
+ sql_data = self.fetch_user_cols()
+
+ total = [0] * len(self.user_cols_meta)
+ total[0] = "Total"
+
+ for data in sql_data:
+ for n in xrange(1, len(data)):
+ if self.user_cols_meta[n][USER_COL_TYPE] == "str":
+ total[n] = ""
+ else:
+ total[n] += data[n] if data[n] else 0
+ sql_data = [total] + sql_data
+
+ user_data = {}
+ for r in sql_data:
+ user_data[r[0]] = r
+
+ if not self.prev_time:
+ self.user_cols_data_prev = user_data
+ self.prev_time = time.time()
+ return None
+
+ self.user_cols_view_data = []
+
+ t = time.time()
+ for data in sql_data:
+ out = []
+
+ table = data[0]
+
+ for n in xrange(0, len(data)):
+ if self.user_cols_meta[n][USER_COL_TYPE] == "str":
+ s = str(data[n])
+ w = self.user_cols_meta[n][USER_COL_WIDTH]
+ if len(s) > w:
+ s = s[0:w-3] + "..."
+ out.append(s)
+ elif self.user_cols_meta[n][USER_COL_ABS]:
+ out.append(data[n])
+ else:
+ new = data[n] if data[n] else 0
+ old = self.user_cols_data_prev[table][n] if self.user_cols_data_prev.has_key(table) and \
+ self.user_cols_data_prev[table][n] else 0
+ out.append(new - old)
+
+ for n in xrange(0, len(out)):
+ if self.user_cols_meta[n][USER_COL_METRIC].endswith("/s"):
+ if t - self.prev_time:
+ out[n] = int(out[n]) / (t - self.prev_time)
+ if self.user_cols_meta[n][USER_COL_TYPE] == "int":
+ out[n] = round(out[n])
+
+ self.user_cols_view_data.append(out)
+
+ self.user_cols_data_prev = user_data
+ self.prev_time = t
+
+ def get_user_cols_view_data(self):
+ return sorted(self.user_cols_view_data, key=lambda x:
+ (x[self.user_cols_sorted],
+ x[self.user_cols_hash['Write']],
+ x[self.user_cols_hash['Reltuples']]),
+ reverse=True)
+
+ def _refresh(self):
+ if not self.scr or self.terminate:
+ return
+ (max_y, max_x) = self.scr.getmaxyx()
+
+ s = sum([c[1] + 1 for c in self.user_cols_meta])
+ s -= self.user_cols_meta[0][USER_COL_WIDTH]
+ self.user_cols_meta[0][USER_COL_WIDTH] = max_x - s
+
+ fmt = []
+ for c in self.user_cols_meta:
+ if c[USER_COL_TYPE] == "int":
+ fmt.append("%%%dd" % c[1])
+ elif c[USER_COL_TYPE] == "float":
+ fmt.append("%%%d.1f" % c[1])
+ else:
+ fmt.append("%%%ds" % c[1])
+ fmt_data = " ".join(fmt)
+ fmt_header = " ".join(["%%%ds" % c[USER_COL_WIDTH] for c in self.user_cols_meta])
+
+ self.scr.erase()
+ self.scr.addstr(0, 0, "%s | Use: 'left' and 'right' keys - select sortable col; 'p' pause; 'q' quit; 'space' refresh"
+ % self.user_cols_view_ctime)
+ if self.paused:
+ self.scr.addstr(0, 0, "PAUSED! ")
+ self.scr.addstr(1, 0, "=" * max_x)
+ columns = []
+ metrics = []
+
+ for n in xrange(0, len(self.user_cols_meta)):
+ c = self.user_cols_meta[n]
+ metrics.append(c[USER_COL_METRIC])
+ if self.user_cols_sorted == n:
+ columns.append("*" + c[USER_COL_NAME])
+ else:
+ columns.append(c[USER_COL_NAME])
+
+ self.scr.addstr(2, 0, fmt_header % tuple(columns))
+ self.scr.addstr(3, 0, fmt_header % tuple(metrics))
+
+ if not self.paused:
+ self.update_user_cols_view()
+ view = self.get_user_cols_view_data()
+
+ if view == None:
+ return
+
+ self.scr.addstr(4, 0, "-" * max_x)
+ r = 4
+ for row in view:
+ r += 1
+ if r == max_y:
+ break
+ self.scr.addstr(r, 0, fmt_data % tuple(row))
+
+ self.scr.refresh()
+
+ def refresh(self):
+ self.mutex.acquire()
+ try:
+ self._refresh()
+ except:
+ self.mutex.release()
+ raise
+ self.mutex.release()
+
+ def shift_sorted_col(self, shift):
+ self.user_cols_sorted = (len(self.user_cols_meta) + self.user_cols_sorted + shift) % len(self.user_cols_meta)
+
+ def handle_key(self, key):
+ if ord(key) == KEY_LEFT:
+ self.shift_sorted_col(-1)
+ elif ord(key) == KEY_RIGHT:
+ self.shift_sorted_col(1)
+ elif key == 'p':
+ self.paused = self.paused ^ 1
+ elif key == ' ':
+ if self.paused:
+ self.paused = 0
+ else:
+ return
+ self.refresh()
+
+ def getkey(self):
+ try:
+ # return chr(self.scr.getch()) - thread unsafe
+ key = sys.stdin.read(1)
+ except KeyboardInterrupt:
+ raise
+ except:
+ return chr(0)
+
+ self.handle_key(key)
+ return key
+
+ def handle_exc(self):
+ print >> sys.stderr, traceback.format_exc()
+ self.mutex.acquire()
+ curses.endwin()
+ self.mutex.release()
+ self.deinit()
+
+ def deinit(self):
+ if isinstance(sys.stderr, StringIO.StringIO):
+ print sys.stderr.getvalue()
+ sys.stderr = sys.stdout
+ sys.stdout.flush()
+
+ def __del__(self):
+ self.deinit()
+
+
+def main_loop(pgt):
+ try:
+ pgt.refresh()
+ time.sleep(0.3)
+ while 1:
+ if not pgt.paused:
+ pgt.refresh()
+ time.sleep(pgt.opts.delay)
+ if pgt.terminate:
+ return
+ except:
+ pgt.handle_exc()
+ os._exit(1)
+
+def pg_top(scr, pgt, con, opts):
+
+ curses.noecho() # disable echo
+ curses.cbreak() # keys are read directly, without hitting Enter
+# curses.curs_set(0) # disable mouse
+
+ pgt.init(scr, con, opts)
+ t = threading.Thread(target=main_loop, args=(pgt,))
+ t.daemon = True
+ t.start()
+
+ while 1:
+ try:
+ key = pgt.getkey()
+ if key == 'q':
+ pgt.terminate = True
+ break
+ except KeyboardInterrupt:
+ break
+ pgt.terminate = True
+
+def main():
+ test_description = "%prog [options]"
+ pgt = PgTop()
+
+ epilog = "\nCounters description:"
+ for c in pgt.user_cols_meta:
+ epilog += "\n%9s - %s" % (c[USER_COL_NAME], c[USER_COL_HELP])
+
+ class PgOptParser(OptionParser):
+ def format_epilog(self, formatter):
+ return self.epilog + "\n"
+
+ p = PgOptParser(test_description, epilog=epilog)
+ p.add_option("-v", "--verbose", action="store_true", help="enable verbose mode")
+ p.add_option("-d", "--delay", type=int, default=2, help="delay between database poll (sec)")
+ p.add_option("-n", "--count", type=int, default=0, help="exit after COUNT iterations")
+ p.add_option("-a", "--abs", action="store_true", help="show absolute values, not rates")
+ p.add_option("-s", "--sort", type="choice", default="Write",
+ choices=tuple([c[USER_COL_NAME] for c in user_cols_def]), help="sort by given column (default is '%default')")
+ p.add_option("-S", "--schema", type="string",
+ help="take into account only given schema (default: all schemas)")
+
+ defdb = ""
+ defusr = "postgres"
+ if HAS_PA:
+ g = OptionGroup(p, "If you have pa.conf")
+ g.add_option("-c", "--config", type="string", default=None, help="PA config file [default: %default]")
+ g.add_option("", "--poa", action="store_true", help = "connect to POA only")
+ g.add_option("", "--pba", action="store_true", help = "connect to PBA only")
+ p.add_option_group(g)
+ defdb = "plesk"
+ defusr = "plesk"
+
+ g = OptionGroup(p, "If you don't have pa.conf" if HAS_PA else "Database credentials")
+ g.add_option("", "--db-host", type="string", help="database hostname/IP")
+ g.add_option("", "--db-port", type="string", default=5432, help="database port")
+ g.add_option("", "--db-name", type="string", default=defdb, help="database name [default: %default]")
+ g.add_option("", "--db-user", type="string", default=defusr, help="database username [default: %default]")
+ g.add_option("", "--db-pass", type="string", default="", help="database password")
+ p.add_option_group(g)
+
+ opts, args = p.parse_args()
+ loglevel = logging.DEBUG if opts.verbose else logging.WARNING
+ logging.basicConfig(level=loglevel, format="%(asctime)s - %(module)s - %(levelname)s - %(message)s")
+
+ dbs = []
+ con = {}
+
+ if HAS_PA:
+ if not opts.config and not opts.db_host:
+ p.error("either -c or --db-host option must be provided")
+ else:
+ if not opts.db_host:
+ p.error("--db-host option must be provided")
+
+ if HAS_PA and opts.config:
+ non = not opts.pba and not opts.poa
+ pa_config.init(opts.config)
+ cfg = pa_config.get()
+ if (non or opts.pba) and cfg.pba_db.host:
+ dbs.append(DB(cfg.pba_db.ip, cfg.pba_db.db_port, cfg.pba_db.db_name, cfg.pba_db.db_user, cfg.pba_db.db_pass))
+ if (non or opts.poa) and cfg.poa_db.host:
+ dbs.append(DB(cfg.poa_db.ip, cfg.poa_db.db_port, cfg.poa_db.db_name, cfg.poa_db.db_user, cfg.poa_db.db_pass))
+ else:
+ dbs.append(DB(opts.db_host, opts.db_port, opts.db_name, opts.db_user, opts.db_pass))
+
+ for db in dbs:
+ print "Connecting to %s..." % str(db)
+ try:
+ con[db.get_name()] = db.connect()
+ except Exception, x:
+ print "failed to connect: ", type(x), str(x)
+
+ if con:
+ try:
+ curses.wrapper(pg_top, pgt, con, opts)
+ except:
+ pgt.handle_exc()
+
+if __name__ == "__main__":
+ main()