]> git.donarmstrong.com Git - neurodebian.git/blob - tools/nd_apachelogs2subscriptionstats
Slight adjustment of the download stats range
[neurodebian.git] / tools / nd_apachelogs2subscriptionstats
1 #!/usr/bin/python
2 # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
3 # vi: set ft=python sts=4 ts=4 sw=4 et:
4 #
5 # Create a figure with the NeuroDebian repo subscription stats from the apache logs
6 # Requires out put of
7 #  zgrep "GET /lists/[-a-z\.]\+ HTTP" neuro.debian.net-*access.log* | sed -e 's,[^:]*:\([0-9\.]\+\).*\[\(.*\):.*:.*:.*/lists/\(.*\) HTTP.*,\2;\3;\1,' -e 's,/, ,g'
8 # either from a file or on stdin. Needs output filename as the only argument
9
10 import fileinput
11 import sys
12 from datetime import datetime
13 import numpy as np
14 import matplotlib
15 matplotlib.use('Agg')
16 import pylab as pl
17 from matplotlib.dates import date2num, num2date
18 from matplotlib.dates import YearLocator, MonthLocator, DateFormatter
19 from matplotlib.font_manager import FontProperties
20 from ConfigParser import SafeConfigParser
21
22
23 dt = [('ip', '|S16'),
24       ('loc', '|S3'),
25       ('suite', '|S20'),
26       ('date', float)]
27
28
29 def make_figure(data, ymax):
30     fig = pl.figure(figsize=(14,3))
31     distros = ('Debian', 'Ubuntu')
32     # Sorting is actually seems to be not needed on Python 2.7
33     # which probably returns release codenames in the order as
34     # in the config file which is already correct
35     # But since our server is still on previous stable release
36     # let's sort for now explicitly
37     # 9999 for 'nd' == 'sid'
38     sorting_ids = dict([(x[0], len(x[1])>2 and float(x[1][2:]) or 9999)
39                         for x in cfg.items('release backport ids')])
40     for idistro, distro in enumerate(distros):
41         ax = fig.add_subplot(1, len(distros), idistro+1)
42         suites = [code for code in cfg.options('release codenames')
43                   if cfg.get('release codenames', code).count(distro)]
44         # sort suites according to backport ids
45         # and in reverse order so the freshiest is on top
46         suites = sorted(suites,
47                         cmp=lambda x,y: cmp(sorting_ids[x], sorting_ids[y]),
48                         reverse=True)
49         plot_datehist(ax, data, 10, suites, title=distro, ymax=ymax)
50     fig.autofmt_xdate()
51     return fig
52
53
54 def plot_datehist(ax, data, bins, suites, title=None, ymax=None):
55     colors=['#ff0088', '#20435C', '#45902C', '#E08720']
56     linestyle=['-', '--']
57     global_x_max = None
58     global_x_min = None
59     global_y_max = None
60     for i, suite in enumerate(suites):
61         dates = data['date'][data['suite'] == suite]
62         # history in days
63         history_length = dates.max() - dates.min()
64         # make approx monthly bins, smaller bins yield spiky curves
65         # needs new=True to work with oldish numpy
66         (hist, bin_edges) = np.histogram(dates, np.ceil(history_length/30.))
67         if False:
68             # debug output ;-)
69             print dates.min(), num2date(dates.min()), dates.max(), \
70                   num2date(dates.max()), history_length
71             print bin_edges
72         if len(bin_edges) < 2:
73             # protect against single data point entries by ignoring them
74             # wouldn't be able to draw a line anyway ;-)
75             continue
76         width = bin_edges[1] - bin_edges[0]
77         # think lines
78         ax.plot(bin_edges[:-1]+(width/2), hist / width,
79                 label=suite, color=colors[i%4], linestyle=linestyle[i//4], lw=2)
80         # transparent curve shading
81         ax.fill_between(bin_edges[:-1]+(width/2), 0, hist / width, alpha=0.2,
82                         label=suite, color=colors[i%4])
83         # figure out axis limits to avoid whitespace in plots
84         x_max = bin_edges[-2] + width/2
85         x_min = bin_edges[0] + width/2
86         if global_x_max is None or x_max > global_x_max:
87             global_x_max = x_max
88         if global_x_min is None or x_min < global_x_min:
89             global_x_min = x_min
90
91     ax.set_xlim(global_x_min, global_x_max)
92     ax.set_ylabel('New subscriptions [1/day]')
93     if title:
94         ax.set_title(title)
95     if ymax:
96         ax.set_ylim(0, ymax)
97
98     # set x-ticks in date
99     # see: http://matplotlib.sourceforge.net/examples/api/date_demo.html
100     ax.xaxis.set_major_locator(YearLocator())
101     ax.xaxis.set_major_formatter(DateFormatter('\n\n%Y'))
102     ax.xaxis.set_minor_locator(MonthLocator())
103     ax.xaxis.set_minor_formatter(DateFormatter('%b'))
104     # format the coords message box
105     ax.format_xdata = DateFormatter('%Y-%m-%d')
106     ax.grid(True)
107     # pukes with old matplotlib
108     #font = FontProperties()
109     #font.set_size = 8
110     pl.legend(loc='upper left', #prop=font,
111               labelspacing=.2, borderaxespad=.2,
112               handletextpad=.2, borderpad=.2)
113
114
115 if __name__ == '__main__':
116     if not len(sys.argv) > 1:
117         print 'Need output filename.'
118         sys.exit(1)
119     cfg_path="/home/neurodebian/neurodebian.git/neurodebian.cfg"
120     #cfg_path="../neurodebian.cfg"
121     cfg = SafeConfigParser()
122     cfg.read(cfg_path)
123     data = []
124     for line in fileinput.FileInput(sys.argv[2:], openhook=fileinput.hook_compressed):
125         date, list_, ip = line.split(';')
126         try:
127             suite, loc = list_.split('.')
128         except ValueError:
129             suite = list_
130             loc = ''
131         date = datetime.strptime(date, "%d %b %Y")
132         data.append((ip.strip(), loc, suite, date2num(date)))
133     data = np.array(data, dtype=dt)
134     make_figure(data, ymax=21).savefig(sys.argv[1], bbox_inches='tight', dpi=60)