chiark / gitweb /
Make the server tools an installable package (with distutils) - wip
[fdroidserver.git] / fdroidserver / stats.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # stats.py - part of the FDroid server tools
5 # Copyright (C) 2010-12, Ciaran Gultnieks, ciaran@ciarang.com
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU Affero General Public License for more details.
16 #
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 import sys
21 import os
22 import shutil
23 import re
24 import urllib
25 import time
26 import traceback
27 import glob
28 from optparse import OptionParser
29 import HTMLParser
30 import paramiko
31 import common
32
33 def main():
34
35     # Read configuration...
36     execfile('config.py', globals())
37
38     # Parse command line...
39     parser = OptionParser()
40     parser.add_option("-v", "--verbose", action="store_true", default=False,
41                       help="Spew out even more information than normal")
42     parser.add_option("-d", "--download", action="store_true", default=False,
43                       help="Download logs we don't have")
44     (options, args) = parser.parse_args()
45
46
47     statsdir = 'stats'
48     logsdir = os.path.join(statsdir, 'logs')
49     logsarchivedir = os.path.join(logsdir, 'archive')
50     datadir = os.path.join(statsdir, 'data')
51     if not os.path.exists(statsdir):
52         os.mkdir(statsdir)
53     if not os.path.exists(logsdir):
54         os.mkdir(logsdir)
55     if not os.path.exists(datadir):
56         os.mkdir(datadir)
57
58     if options.download:
59         # Get any access logs we don't have...
60         ssh = None
61         ftp = None
62         try:
63             print 'Retrieving logs'
64             ssh = paramiko.SSHClient()
65             ssh.load_system_host_keys()
66             ssh.connect('f-droid.org', username='fdroid', timeout=10,
67                     key_filename=webserver_keyfile)
68             ftp = ssh.open_sftp()
69             ftp.get_channel().settimeout(15)
70             print "...connected"
71
72             ftp.chdir('logs')
73             files = ftp.listdir()
74             for f in files:
75                 if f.startswith('access-') and f.endswith('.log'):
76
77                     destpath = os.path.join(logsdir, f)
78                     archivepath = os.path.join(logsarchivedir, f + '.gz')
79                     if os.path.exists(archivepath):
80                         if os.path.exists(destpath):
81                             # Just in case we have it archived but failed to remove
82                             # the original...
83                             os.remove(destpath)
84                     else:
85                         destsize = ftp.stat(f).st_size
86                         if (not os.path.exists(destpath) or
87                                 os.path.getsize(destpath) != destsize):
88                             print "...retrieving " + f
89                             ftp.get(f, destpath)
90         except Exception as e:
91             traceback.print_exc()
92             sys.exit(1)
93         finally:
94             #Disconnect
95             if ftp != None:
96                 ftp.close()
97             if ssh != None:
98                 ssh.close()
99
100     # Process logs
101     logexpr = '(?P<ip>[.:0-9a-fA-F]+) - - \[(?P<time>.*?)\] "GET (?P<uri>.*?) HTTP/1.\d" (?P<statuscode>\d+) \d+ "(?P<referral>.*?)" "(?P<useragent>.*?)"'
102     logsearch = re.compile(logexpr).search
103     apps = {}
104     unknownapks = []
105     knownapks = common.KnownApks()
106     for logfile in glob.glob(os.path.join(logsdir,'access-*.log')):
107         logdate = logfile[len(logsdir) + 1 + len('access-'):-4]
108         matches = (logsearch(line) for line in file(logfile))
109         for match in matches:
110             if match and match.group('statuscode') == '200':
111                 uri = match.group('uri')
112                 if uri.endswith('.apk'):
113                     _, apkname = os.path.split(uri)
114                     app = knownapks.getapp(apkname)
115                     if app:
116                         appid, _ = app
117                         if appid in apps:
118                             apps[appid] += 1
119                         else:
120                             apps[appid] = 1
121                     else:
122                         if not apkname in unknownapks:
123                             unknownapks.append(apkname)
124
125     # Calculate and write stats for total downloads...
126     f = open('stats/total_downloads_app.txt', 'w')
127     lst = []
128     alldownloads = 0
129     for app, count in apps.iteritems():
130         lst.append(app + " " + str(count))
131         alldownloads += count
132     lst.append("ALL " + str(alldownloads))
133     f.write('# Total downloads by application, since October 2011\n')
134     for line in sorted(lst):
135         f.write(line + '\n')
136     f.close()
137
138     # Write list of latest apps added to the repo...
139     latest = knownapks.getlatest(10)
140     f = open('stats/latestapps.txt', 'w')
141     for app in latest:
142         f.write(app + '\n')
143     f.close()
144
145     if len(unknownapks) > 0:
146         print '\nUnknown apks:'
147         for apk in unknownapks:
148             print apk
149
150     print "Finished."
151
152 if __name__ == "__main__":
153     main()
154