~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/python
#
# Copyright 2009 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""
Generate some statistics about a PostgreSQL database suitable for
emailing via cron
"""

__metaclass__ = type

import sys
import psycopg2

def percentage(num, total):
    """Return a percentage string of num/total"""
    if total == 0:
        return 'Unknown'
    else:
        return '%3.2f%%' % ( (num * 100.0) / total, )


def print_row(key, value):
    print '%(key)-20s: %(value)s' % vars()


def pgstattuple(cur, table):
    """Return the result of PostgreSQL contribs's pgstattuple function
    """
    cur.execute("""
        SELECT
            table_len, tuple_count, tuple_len, tuple_percent,
            dead_tuple_count, dead_tuple_len, dead_tuple_percent,
            free_space, free_percent
        FROM pgstattuple(%(table)s)
        """, vars())
    pgstattuple = cur.fetchone()
    return {
        'name': table,
        'table_len': pgstattuple[0],
        'tuple_count': pgstattuple[1],
        'tuple_len': pgstattuple[2],
        'tuple_percent': pgstattuple[3],
        'dead_tuple_count': pgstattuple[4],
        'dead_tuple_len': pgstattuple[5],
        'dead_tuple_percent': pgstattuple[6],
        'free_space': pgstattuple[7],
        'free_percent': pgstattuple[8],
        }


def main(dbname):
    con = psycopg2.connect("dbname=%s" % dbname)
    cur = con.cursor()

    print 'Statistics for %s' % dbname
    print '===============' + '=' * (len(dbname))

    # Database level statistics
    cur.execute("""
        SELECT blks_hit, blks_read, numbackends,xact_commit, xact_rollback
            FROM pg_stat_database
            WHERE datname=%(dbname)s
        """, vars())
    hit, read, backends, commits, rollbacks = cur.fetchone()

    hit_rate = percentage(hit, hit + read)

    print_row("Cache hit rate", hit_rate)
    print_row("Number of backends", backends)

    commit_rate = percentage(commits, commits + rollbacks)

    print_row("Commit rate", commit_rate)

    # Determine dead tuple bloat, if we have pgstattuple installed
    cur.execute("""
        SELECT COUNT(*) FROM pg_proc, pg_namespace
        WHERE pg_proc.pronamespace = pg_namespace.oid
            AND pg_namespace.nspname = 'public'
            AND proname = 'pgstattuple'
        """)
    pgstattuple_installed = (cur.fetchone()[0] > 0)
    if pgstattuple_installed:
        cur.execute("""
            SELECT nspname || '.' || relname
            FROM pg_class, pg_namespace
            WHERE pg_class.relnamespace = pg_namespace.oid
                AND pg_class.relkind = 'r'
                ORDER BY nspname, relname
            """)
        all_tables = [r[0] for r in cur.fetchall()]
        total_live_bytes = 0
        total_dead_bytes = 0
        stats = []
        for table in all_tables:
            stat = pgstattuple(cur, table)
            total_live_bytes += stat['tuple_len']
            total_dead_bytes += stat['dead_tuple_len']
            stats.append(stat)
        # Just report the worst offenders
        stats.sort(key=lambda x: x['dead_tuple_percent'], reverse=True)
        stats = [
            s for s in stats if s['dead_tuple_percent'] >= 10
                and s['dead_tuple_len'] >= 25 * 1024 * 1024
            ]
        def statstr(stat):
            name = stat['name']
            dead_tuple_percent = stat['dead_tuple_percent']
            dead_len = stat['dead_tuple_len'] / (1024*1024)
            return (
                    '%(name)s (%(dead_len)0.2fMB, '
                    '%(dead_tuple_percent)0.2f%%)' % vars()
                    )
        if len(stats) > 0:
            print_row('Needing vacuum', statstr(stats[0]))
            for stat in stats[1:]:
                print_row('', statstr(stat))

    # Unused indexes, ignoring primary keys.
    # XXX Stuart Bishop 2005-06-28:
    # We should identify constraints used to enforce uniqueness too
    cur.execute("""
        SELECT relname, indexrelname
            FROM pg_stat_user_indexes AS u JOIN pg_indexes AS i
                ON u.schemaname = i.schemaname
                    AND u.relname = i.tablename
                    AND u.indexrelname = i.indexname
            WHERE
                idx_scan = 0
                AND indexrelname NOT LIKE '%_pkey'
                AND indexdef NOT LIKE 'CREATE UNIQUE %'
            ORDER BY relname, indexrelname
        """)

    rows = cur.fetchall()
    if len(rows) == 0:
        print_row('Unused indexes', 'N/A')
    else:
        print_row('Unused indexes', rows[0][1])
        for table, index in rows[1:]:
            print_row('', index)

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print >> sys.stderr, "Usage: %s [DBNAME]" % sys.argv[0]
        sys.exit(1)
    dbname = sys.argv[1]
    main(dbname)