~launchpad-pqm/launchpad/devel

10637.3.1 by Guilherme Salgado
Use the default python version instead of a hard-coded version
1
#!/usr/bin/python
8687.15.4 by Karl Fogel
Add the copyright header block to more files; tweak format in a few files.
2
#
3
# Copyright 2009 Canonical Ltd.  This software is licensed under the
4
# GNU Affero General Public License version 3 (see the file LICENSE).
5
1982 by Canonical.com Patch Queue Manager
[r=spiv] report some PostgreSQL stats we need to see
6
"""
7
Generate some statistics about a PostgreSQL database suitable for
8
emailing via cron
9
"""
10
11
__metaclass__ = type
12
13
import sys
5821.2.85 by James Henstridge
Add "make check_launchpad_storm_on_merge" target that runs the tests
14
import psycopg2
1982 by Canonical.com Patch Queue Manager
[r=spiv] report some PostgreSQL stats we need to see
15
16
def percentage(num, total):
17
    """Return a percentage string of num/total"""
18
    if total == 0:
19
        return 'Unknown'
20
    else:
21
        return '%3.2f%%' % ( (num * 100.0) / total, )
22
23
24
def print_row(key, value):
25
    print '%(key)-20s: %(value)s' % vars()
26
27
1681.1.90 by Stuart Bishop
Sync pgstats.py with production
28
def pgstattuple(cur, table):
29
    """Return the result of PostgreSQL contribs's pgstattuple function
30
    """
31
    cur.execute("""
32
        SELECT
33
            table_len, tuple_count, tuple_len, tuple_percent,
34
            dead_tuple_count, dead_tuple_len, dead_tuple_percent,
35
            free_space, free_percent
36
        FROM pgstattuple(%(table)s)
37
        """, vars())
38
    pgstattuple = cur.fetchone()
39
    return {
40
        'name': table,
41
        'table_len': pgstattuple[0],
42
        'tuple_count': pgstattuple[1],
43
        'tuple_len': pgstattuple[2],
44
        'tuple_percent': pgstattuple[3],
45
        'dead_tuple_count': pgstattuple[4],
46
        'dead_tuple_len': pgstattuple[5],
47
        'dead_tuple_percent': pgstattuple[6],
48
        'free_space': pgstattuple[7],
49
        'free_percent': pgstattuple[8],
50
        }
51
52
1982 by Canonical.com Patch Queue Manager
[r=spiv] report some PostgreSQL stats we need to see
53
def main(dbname):
5821.2.85 by James Henstridge
Add "make check_launchpad_storm_on_merge" target that runs the tests
54
    con = psycopg2.connect("dbname=%s" % dbname)
1982 by Canonical.com Patch Queue Manager
[r=spiv] report some PostgreSQL stats we need to see
55
    cur = con.cursor()
56
57
    print 'Statistics for %s' % dbname
58
    print '===============' + '=' * (len(dbname))
59
60
    # Database level statistics
61
    cur.execute("""
62
        SELECT blks_hit, blks_read, numbackends,xact_commit, xact_rollback
63
            FROM pg_stat_database
64
            WHERE datname=%(dbname)s
65
        """, vars())
66
    hit, read, backends, commits, rollbacks = cur.fetchone()
67
68
    hit_rate = percentage(hit, hit + read)
69
70
    print_row("Cache hit rate", hit_rate)
71
    print_row("Number of backends", backends)
72
73
    commit_rate = percentage(commits, commits + rollbacks)
74
75
    print_row("Commit rate", commit_rate)
76
1681.1.90 by Stuart Bishop
Sync pgstats.py with production
77
    # Determine dead tuple bloat, if we have pgstattuple installed
78
    cur.execute("""
79
        SELECT COUNT(*) FROM pg_proc, pg_namespace
80
        WHERE pg_proc.pronamespace = pg_namespace.oid
81
            AND pg_namespace.nspname = 'public'
82
            AND proname = 'pgstattuple'
83
        """)
84
    pgstattuple_installed = (cur.fetchone()[0] > 0)
85
    if pgstattuple_installed:
86
        cur.execute("""
87
            SELECT nspname || '.' || relname
88
            FROM pg_class, pg_namespace
89
            WHERE pg_class.relnamespace = pg_namespace.oid
90
                AND pg_class.relkind = 'r'
91
                ORDER BY nspname, relname
92
            """)
93
        all_tables = [r[0] for r in cur.fetchall()]
94
        total_live_bytes = 0
95
        total_dead_bytes = 0
96
        stats = []
97
        for table in all_tables:
98
            stat = pgstattuple(cur, table)
99
            total_live_bytes += stat['tuple_len']
100
            total_dead_bytes += stat['dead_tuple_len']
101
            stats.append(stat)
102
        # Just report the worst offenders
103
        stats.sort(key=lambda x: x['dead_tuple_percent'], reverse=True)
104
        stats = [
105
            s for s in stats if s['dead_tuple_percent'] >= 10
106
                and s['dead_tuple_len'] >= 25 * 1024 * 1024
107
            ]
108
        def statstr(stat):
109
            name = stat['name']
110
            dead_tuple_percent = stat['dead_tuple_percent']
111
            dead_len = stat['dead_tuple_len'] / (1024*1024)
112
            return (
113
                    '%(name)s (%(dead_len)0.2fMB, '
114
                    '%(dead_tuple_percent)0.2f%%)' % vars()
115
                    )
116
        if len(stats) > 0:
117
            print_row('Needing vacuum', statstr(stats[0]))
118
            for stat in stats[1:]:
119
                print_row('', statstr(stat))
120
1982 by Canonical.com Patch Queue Manager
[r=spiv] report some PostgreSQL stats we need to see
121
    # Unused indexes, ignoring primary keys.
4664.1.1 by Curtis Hovey
Normalized comments for bug 3732.
122
    # XXX Stuart Bishop 2005-06-28:
123
    # We should identify constraints used to enforce uniqueness too
1982 by Canonical.com Patch Queue Manager
[r=spiv] report some PostgreSQL stats we need to see
124
    cur.execute("""
125
        SELECT relname, indexrelname
126
            FROM pg_stat_user_indexes AS u JOIN pg_indexes AS i
127
                ON u.schemaname = i.schemaname
128
                    AND u.relname = i.tablename
129
                    AND u.indexrelname = i.indexname
130
            WHERE
131
                idx_scan = 0
132
                AND indexrelname NOT LIKE '%_pkey'
133
                AND indexdef NOT LIKE 'CREATE UNIQUE %'
1681.1.127 by Stuart Bishop
Order unused index report
134
            ORDER BY relname, indexrelname
1982 by Canonical.com Patch Queue Manager
[r=spiv] report some PostgreSQL stats we need to see
135
        """)
136
137
    rows = cur.fetchall()
138
    if len(rows) == 0:
139
        print_row('Unused indexes', 'N/A')
140
    else:
141
        print_row('Unused indexes', rows[0][1])
142
        for table, index in rows[1:]:
143
            print_row('', index)
144
145
if __name__ == '__main__':
146
    if len(sys.argv) != 2:
147
        print >> sys.stderr, "Usage: %s [DBNAME]" % sys.argv[0]
148
        sys.exit(1)
149
    dbname = sys.argv[1]
150
    main(dbname)