~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to utilities/report-database-stats.py

Merged db-devel into package-build-recipe.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python2.5 -S
 
1
#!/usr/bin/python -S
2
2
# Copyright 2010 Canonical Ltd.  This software is licensed under the
3
3
# GNU Affero General Public License version 3 (see the file LICENSE).
4
4
 
8
8
 
9
9
import _pythonpath
10
10
 
 
11
from datetime import datetime
11
12
from operator import attrgetter
 
13
from textwrap import dedent
12
14
 
13
15
from canonical.database.sqlbase import connect, sqlvalues
14
16
from canonical.launchpad.scripts import db_options
19
21
    pass
20
22
 
21
23
 
 
24
def get_where_clause(options):
 
25
    "Generate a WHERE clause referencing the date_created column."
 
26
    # We have two of the from timestamp, the until timestamp and an
 
27
    # interval. The interval is in a format unsuitable for processing in
 
28
    # Python. If the interval is set, it represents the period before
 
29
    # the until timestamp or the period after the from timestamp,
 
30
    # depending on which of these is set. From this information,
 
31
    # generate the SQL representation of the from timestamp and the
 
32
    # until timestamp.
 
33
    if options.from_ts:
 
34
        from_sql = ("CAST(%s AS timestamp without time zone)"
 
35
            % sqlvalues(options.from_ts))
 
36
    elif options.interval and options.until_ts:
 
37
        from_sql = (
 
38
            "CAST(%s AS timestamp without time zone) - CAST(%s AS interval)"
 
39
            % sqlvalues(options.until_ts, options.interval))
 
40
    elif options.interval:
 
41
        from_sql = (
 
42
            "(CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - CAST(%s AS interval)"
 
43
            % sqlvalues(options.interval))
 
44
    else:
 
45
        from_sql = "CAST('1970-01-01' AS timestamp without time zone)"
 
46
 
 
47
    if options.until_ts:
 
48
        until_sql = (
 
49
            "CAST(%s AS timestamp without time zone)"
 
50
            % sqlvalues(options.until_ts))
 
51
    elif options.interval and options.from_ts:
 
52
        until_sql = (
 
53
            "CAST(%s AS timestamp without time zone) + CAST(%s AS interval)"
 
54
            % sqlvalues(options.from_ts, options.interval))
 
55
    else:
 
56
        until_sql = "CURRENT_TIMESTAMP AT TIME ZONE 'UTC'"
 
57
 
 
58
    clause = "date_created BETWEEN (%s) AND (%s)" % (from_sql, until_sql)
 
59
 
 
60
    return clause
 
61
 
 
62
 
22
63
def get_table_stats(cur, options):
23
 
    tablestats_query = """
 
64
    params = {'where': get_where_clause(options)}
 
65
    tablestats_query = dedent("""\
24
66
        SELECT
25
67
            Earliest.date_created AS date_start,
26
68
            Latest.date_created AS date_end,
46
88
        WHERE
47
89
            Earliest.date_created = (
48
90
                SELECT min(date_created) FROM DatabaseTableStats
49
 
                WHERE date_created >= CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
50
 
                    - CAST(%s AS interval))
 
91
                WHERE %(where)s)
51
92
            AND Latest.date_created = (
52
 
                SELECT max(date_created) FROM DatabaseTableStats)
 
93
                SELECT max(date_created) FROM DatabaseTableStats
 
94
                WHERE %(where)s)
53
95
            AND Earliest.schemaname = Latest.schemaname
54
96
            AND Earliest.relname = Latest.relname
55
 
        """ % sqlvalues(options.since_interval)
56
 
 
 
97
        """ % params)
57
98
    cur.execute(tablestats_query)
58
99
 
59
100
    # description[0] is the column name, per PEP-0249
75
116
    # This query calculates the averate cpu utilization from the
76
117
    # samples. It assumes samples are taken at regular intervals over
77
118
    # the period.
78
 
    query = """
 
119
    # Note that we have to use SUM()/COUNT() instead of AVG() as
 
120
    # database users not connected when the sample was taken are not
 
121
    # recorded - we want the average utilization over the time period,
 
122
    # not the subset of the time period the user was actually connected.
 
123
    params = {'where': get_where_clause(options)}
 
124
    query = dedent("""\
79
125
        SELECT (
80
126
            CAST(SUM(cpu) AS float) / (
81
127
                SELECT COUNT(DISTINCT date_created) FROM DatabaseCpuStats
82
 
                WHERE
83
 
                    date_created >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC')
84
 
                    - CAST (%s AS interval))
85
 
            ) AS avg_cpu, username
 
128
                WHERE %(where)s
 
129
            )) AS avg_cpu, username
86
130
        FROM DatabaseCpuStats
87
 
        WHERE date_created >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
88
 
            - CAST(%s AS interval))
 
131
        WHERE %(where)s
89
132
        GROUP BY username
90
 
        """ % sqlvalues(options.since_interval, options.since_interval)
91
 
 
 
133
        """ % params)
92
134
    cur.execute(query)
93
 
 
94
135
    return set(cur.fetchall())
95
136
 
96
137
 
98
139
    parser = LPOptionParser()
99
140
    db_options(parser)
100
141
    parser.add_option(
101
 
        "-i", "--interval", dest="since_interval", type=str,
102
 
        default="100 years", metavar="INTERVAL",
 
142
        "-f", "--from", dest="from_ts", type=datetime,
 
143
        default=None, metavar="TIMESTAMP",
 
144
        help="Use statistics collected since TIMESTAMP.")
 
145
    parser.add_option(
 
146
        "-u", "--until", dest="until_ts", type=datetime,
 
147
        default=None, metavar="TIMESTAMP",
 
148
        help="Use statistics collected up until TIMESTAMP.")
 
149
    parser.add_option(
 
150
        "-i", "--interval", dest="interval", type=str,
 
151
        default=None, metavar="INTERVAL",
103
152
        help=
104
153
            "Use statistics collected over the last INTERVAL period. "
105
154
            "INTERVAL is a string parsable by PostgreSQL "
111
160
    parser.set_defaults(dbuser="database_stats_report")
112
161
    options, args = parser.parse_args()
113
162
 
 
163
    if options.from_ts and options.until_ts and options.interval:
 
164
        parser.error(
 
165
            "Only two of --from, --until and --interval may be specified.")
 
166
 
114
167
    con = connect(options.dbuser)
115
168
    cur = con.cursor()
116
169
 
117
 
    tables = get_table_stats(cur, options)
118
 
    arbitrary_table = list(tables)[0]
 
170
    tables = list(get_table_stats(cur, options))
 
171
    if len(tables) == 0:
 
172
        parser.error("No statistics available in that time range.")
 
173
    arbitrary_table = tables[0]
119
174
    interval = arbitrary_table.date_end - arbitrary_table.date_start
120
175
    per_second = float(interval.days * 24 * 60 * 60 + interval.seconds)
121
176