]> git.decadent.org.uk Git - dak.git/blob - dak/stats.py
Merge commit 'ftpmaster/master' into sqlalchemy
[dak.git] / dak / stats.py
1 #!/usr/bin/env python
2
3 """ Various statistical pr0nography fun and games """
4 # Copyright (C) 2000, 2001, 2002, 2003, 2006  James Troup <james@nocrew.org>
5
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20 ################################################################################
21
22 # <aj>    can we change the standards instead?
23 # <neuro> standards?
24 # <aj>    whatever we're not conforming to
25 # <aj>    if there's no written standard, why don't we declare linux as
26 #         the defacto standard
27 # <aj>    go us!
28
29 # [aj's attempt to avoid ABI changes for released architecture(s)]
30
31 ################################################################################
32
33 import sys
34 import apt_pkg
35
36 from daklib import utils
37 from daklib.dbconn import DBConn, get_suite_architectures, Suite, Architecture, \
38                           BinAssociation
39
40 ################################################################################
41
42 Cnf = None
43
44 ################################################################################
45
46 def usage(exit_code=0):
47     print """Usage: dak stats MODE
48 Print various stats.
49
50   -h, --help                show this help and exit.
51
52 The following MODEs are available:
53
54   arch-space    - displays space used by each architecture
55   pkg-nums      - displays the number of packages by suite/architecture
56   daily-install - displays daily install stats suitable for graphing
57 """
58     sys.exit(exit_code)
59
60 ################################################################################
61
62 def per_arch_space_use():
63     session = DBConn().session()
64     q = session.execute("""
65 SELECT a.arch_string as Architecture, sum(f.size) AS sum
66   FROM files f, binaries b, architecture a
67   WHERE a.id=b.architecture AND f.id=b.file
68   GROUP BY a.arch_string ORDER BY sum""").fetchall()
69     for j in q:
70         print "%-15.15s %s" % (j[0], j[1])
71     print
72     q = session.execute("SELECT sum(size) FROM files WHERE filename ~ '.(diff.gz|tar.gz|dsc)$'").fetchall()
73     print "%-15.15s %s" % ("Source", q[0][0])
74
75 ################################################################################
76
77 def daily_install_stats():
78     stats = {}
79     f = utils.open_file("2001-11")
80     for line in f.readlines():
81         split = line.strip().split('|')
82         program = split[1]
83         if program != "katie" and program != "process-accepted":
84             continue
85         action = split[2]
86         if action != "installing changes" and action != "installed":
87             continue
88         date = split[0][:8]
89         if not stats.has_key(date):
90             stats[date] = {}
91             stats[date]["packages"] = 0
92             stats[date]["size"] = 0.0
93         if action == "installing changes":
94             stats[date]["packages"] += 1
95         elif action == "installed":
96             stats[date]["size"] += float(split[5])
97
98     dates = stats.keys()
99     dates.sort()
100     for date in dates:
101         packages = stats[date]["packages"]
102         size = int(stats[date]["size"] / 1024.0 / 1024.0)
103         print "%s %s %s" % (date, packages, size)
104
105 ################################################################################
106
107 def longest(list):
108     longest = 0
109     for i in list:
110         l = len(i)
111         if l > longest:
112             longest = l
113     return longest
114
115 def suite_sort(a, b):
116     if Cnf.has_key("Suite::%s::Priority" % (a)):
117         a_priority = int(Cnf["Suite::%s::Priority" % (a)])
118     else:
119         a_priority = 0
120     if Cnf.has_key("Suite::%s::Priority" % (b)):
121         b_priority = int(Cnf["Suite::%s::Priority" % (b)])
122     else:
123         b_priority = 0
124     return cmp(a_priority, b_priority)
125
126 def output_format(suite):
127     output_suite = []
128     for word in suite.split("-"):
129         output_suite.append(word[0])
130     return "-".join(output_suite)
131
132 # Obvious query with GROUP BY and mapped names                  -> 50 seconds
133 # GROUP BY but ids instead of suite/architecture names          -> 28 seconds
134 # Simple query                                                  -> 14 seconds
135 # Simple query into large dictionary + processing               -> 21 seconds
136 # Simple query into large pre-created dictionary + processing   -> 18 seconds
137
138 def number_of_packages():
139     arches = {}
140     arch_ids = {}
141     suites = {}
142     suite_ids = {}
143     d = {}
144     session = DBConn().session()
145     # Build up suite mapping
146     for i in session.query(Suite).all():
147         suites[i.suite_id] = i.suite_name
148         suite_ids[i.suite_name] = i.suite_id
149     # Build up architecture mapping
150     for i in session.query(Architecture).all():
151         arches[i.arch_id] = i.arch_string
152         arch_ids[i.arch_string] = i.arch_id
153     # Pre-create the dictionary
154     for suite_id in suites.keys():
155         d[suite_id] = {}
156         for arch_id in arches.keys():
157             d[suite_id][arch_id] = 0
158     # Get the raw data for binaries
159     # Simultate 'GROUP by suite, architecture' with a dictionary
160     # XXX: Why don't we just get the DB to do this?
161     for i in session.query(BinAssociation):
162         suite_id = i.suite_id
163         arch_id = i.binary.arch_id
164         d[suite_id][arch_id] = d[suite_id][arch_id] + 1
165     # Get the raw data for source
166     arch_id = arch_ids["source"]
167     for i in session.execute('SELECT suite, COUNT(suite) FROM src_associations GROUP BY suite').all():
168         (suite_id, count) = i
169         d[suite_id][arch_id] = d[suite_id][arch_id] + count
170     ## Print the results
171     # Setup
172     suite_list = suites.values()
173     suite_list.sort(suite_sort)
174     suite_id_list = []
175     suite_arches = {}
176     for suite in suite_list:
177         suite_id = suite_ids[suite]
178         suite_arches[suite_id] = {}
179         for arch in get_suite_architectures(suite):
180             suite_arches[suite_id][arch.arch_string] = ""
181         suite_id_list.append(suite_id)
182     output_list = [ output_format(i) for i in suite_list ]
183     longest_suite = longest(output_list)
184     arch_list = arches.values()
185     arch_list.sort()
186     longest_arch = longest(arch_list)
187     # Header
188     output = (" "*longest_arch) + " |"
189     for suite in output_list:
190         output = output + suite.center(longest_suite)+" |"
191     output = output + "\n"+(len(output)*"-")+"\n"
192     # per-arch data
193     arch_list = arches.values()
194     arch_list.sort()
195     longest_arch = longest(arch_list)
196     for arch in arch_list:
197         arch_id = arch_ids[arch]
198         output = output + arch.center(longest_arch)+" |"
199         for suite_id in suite_id_list:
200             if suite_arches[suite_id].has_key(arch):
201                 count = repr(d[suite_id][arch_id])
202             else:
203                 count = "-"
204             output = output + count.rjust(longest_suite)+" |"
205         output = output + "\n"
206     print output
207
208 ################################################################################
209
210 def main ():
211     global Cnf
212
213     Cnf = utils.get_conf()
214     Arguments = [('h',"help","Stats::Options::Help")]
215     for i in [ "help" ]:
216         if not Cnf.has_key("Stats::Options::%s" % (i)):
217             Cnf["Stats::Options::%s" % (i)] = ""
218
219     args = apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
220
221     Options = Cnf.SubTree("Stats::Options")
222     if Options["Help"]:
223         usage()
224
225     if len(args) < 1:
226         utils.warn("dak stats requires a MODE argument")
227         usage(1)
228     elif len(args) > 1:
229         utils.warn("dak stats accepts only one MODE argument")
230         usage(1)
231     mode = args[0].lower()
232
233     if mode == "arch-space":
234         per_arch_space_use()
235     elif mode == "pkg-nums":
236         number_of_packages()
237     elif mode == "daily-install":
238         daily_install_stats()
239     else:
240         utils.warn("unknown mode '%s'" % (mode))
241         usage(1)
242
243 ################################################################################
244
245 if __name__ == '__main__':
246     main()