1 # -*- coding: iso-8859-1 -*-
3 MoinMoin - Hitcount Statistics
5 This macro creates a hitcount chart from the data in "event.log".
7 TODO: refactor to use a class, this code is ugly.
8 A lot of code here is duplicated in stats.useragents.
9 Maybe both can use same base class, maybe some parts are useful to other code.
11 @copyright: 2002-2004 Juergen Hermann <jh@web.de>,
13 @license: GNU GPL, see COPYING for details.
20 from MoinMoin import caching, wikiutil, logfile
21 from MoinMoin.Page import Page
22 from MoinMoin.logfile import eventlog
24 # this is a CONSTANT used for on-disk caching, it must NOT be configurable and
25 # not depend on request.user!
26 DATE_FMT = '%04d-%02d-%02d' # % (y, m, d)
28 def linkto(pagename, request, params=''):
31 if not request.cfg.chart_options:
32 return text(pagename, request, params)
35 return draw(pagename, request)
37 page = Page(request, pagename)
39 # Create escaped query string from dict and params
40 querystr = {'action': 'chart', 'type': 'hitcounts'}
41 querystr = wikiutil.makeQueryString(querystr)
42 querystr = wikiutil.escape(querystr)
44 querystr += '&' + params
46 data = {'url': page.url(request, querystr)}
47 data.update(request.cfg.chart_options)
48 result = ('<img src="%(url)s" width="%(width)d" height="%(height)d"'
49 ' alt="hitcounts chart">') % data
54 def get_data(pagename, request, filterpage=None):
55 cache_days, cache_views, cache_edits = [], [], []
58 # Get results from cache
60 arena = Page(request, pagename)
61 cache = caching.CacheEntry(request, arena, 'hitcounts', scope='item', use_pickle=True)
64 cache = caching.CacheEntry(request, arena, 'hitcounts', scope='wiki', use_pickle=True)
68 cache_date, cache_days, cache_views, cache_edits = cache.content()
70 cache.remove() # cache gone bad
72 # Get new results from the log
73 log = eventlog.EventLog(request)
76 except logfile.LogMissing:
85 if new_date is not None:
86 log.set_filter(['VIEWPAGE', 'SAVEPAGE'])
88 for event in log.reverse():
89 # don't use event_log.date()
92 event_usecs = event[0]
93 if event_usecs <= cache_date:
95 eventpage = event[2].get('pagename', '')
96 if filterpage and eventpage != filterpage:
98 event_secs = wikiutil.version2timestamp(event_usecs)
99 time_tuple = time.gmtime(event_secs) # must be UTC
100 day = tuple(time_tuple[0:3])
101 if day != ratchet_day:
104 ratchet_time -= 86400 # seconds per day
105 rday = tuple(time.gmtime(ratchet_time)[0:3]) # must be UTC
108 days.append(DATE_FMT % rday)
111 days.append(DATE_FMT % day)
115 ratchet_time = event_secs
116 if event[1] == 'VIEWPAGE':
118 elif event[1] == 'SAVEPAGE':
125 # merge the day on the end of the cache
126 if cache_days and days and days[0] == cache_days[-1]:
127 cache_edits[-1] += edits[0]
128 cache_views[-1] += views[0]
129 days, views, edits = days[1:], views[1:], edits[1:]
131 # Update and save the cache
132 cache_days.extend(days)
133 cache_views.extend(views)
134 cache_edits.extend(edits)
135 if new_date is not None:
136 cache.update((latest, cache_days, cache_views, cache_edits))
138 return cache_days, cache_views, cache_edits
141 def text(pagename, request, params=''):
142 from MoinMoin.util.dataset import TupleDataset, Column
143 from MoinMoin.widget.browser import DataBrowserWidget
148 if params.startswith('page='):
149 filterpage = wikiutil.url_unquote(params[len('page='):])
151 if request and request.values and 'page' in request.values:
152 filterpage = request.values['page']
154 days, views, edits = get_data(pagename, request, filterpage)
156 hits = TupleDataset()
157 hits.columns = [Column('day', label=_("Date"), align='left'),
158 Column('views', label=_("Views/day"), align='right'),
159 Column('edits', label=_("Edits/day"), align='right'),
164 if maxentries < len(days):
165 step = float(len(days))/ maxentries
174 for i in xrange(len(days)-1, -1, -1):
175 d, v, e = days[i], views[i], edits[i]
176 # sum up views and edits to step days
183 hits.addRow((d, "%.1f" % (sv/sd), "%.1f" % (se/sd)))
188 table = DataBrowserWidget(request)
190 return table.render(method="GET")
193 def draw(pagename, request):
194 import shutil, cStringIO
195 from MoinMoin.stats.chart import Chart, ChartData, Color
201 if request and request.values and 'page' in request.values:
202 filterpage = request.values['page']
204 days, views, edits = get_data(pagename, request, filterpage)
209 scalefactor = float(max(views))/max(edits)
210 except (ZeroDivisionError, ValueError):
213 scalefactor = int(10 ** math.floor(math.log10(scalefactor)))
216 edits = [x * scalefactor for x in edits]
219 image = cStringIO.StringIO()
221 c.addData(ChartData(views, color='green'))
222 c.addData(ChartData(edits, color='red'))
224 if request.cfg.sitename:
225 chart_title = "%s: " % request.cfg.sitename
226 chart_title = chart_title + _('Page hits and edits')
228 chart_title = _("%(chart_title)s for %(filterpage)s") % {
229 'chart_title': chart_title,
230 'filterpage': filterpage,
232 chart_title = "%s\n%sx%d" % (chart_title, _("green=view\nred=edit"), scalefactor)
234 title=chart_title.encode('iso-8859-1', 'replace'), # gdchart can't do utf-8
235 xtitle=(_('date') + ' (Server)').encode('iso-8859-1', 'replace'),
236 ytitle=_('# of hits').encode('iso-8859-1', 'replace'),
237 title_font=c.GDC_GIANT,
238 #thumblabel = 'THUMB', thumbnail = 1, thumbval = 10,
239 #ytitle_color = Color('green'),
241 #ytitle2 = '# of edits',
242 #ytitle2_color = Color('red'),
243 #ylabel2_color = Color('black'),
246 requested_yinterval=1.0,
247 stack_type=c.GDC_STACK_BESIDE
250 (request.cfg.chart_options['width'], request.cfg.chart_options['height']),
253 request.content_type = 'image/gif'
254 request.content_length = len(image.getvalue())
258 shutil.copyfileobj(image, request, 8192)