|
tw-public@0
|
1 |
# -*- coding: iso-8859-1 -*-
|
|
tw-public@0
|
2 |
"""
|
|
tw-public@0
|
3 |
MoinMoin - Hitcount Statistics
|
|
tw-public@0
|
4 |
|
|
tw-public@0
|
5 |
This macro creates a hitcount chart from the data in "event.log".
|
|
tw-public@0
|
6 |
|
|
tw@1832
|
7 |
TODO: refactor to use a class, this code is ugly.
|
|
tw@1832
|
8 |
A lot of code here is duplicated in stats.useragents.
|
|
tw@1832
|
9 |
Maybe both can use same base class, maybe some parts are useful to other code.
|
|
tw-public@0
|
10 |
|
|
tw@2970
|
11 |
@copyright: 2002-2004 Juergen Hermann <jh@web.de>,
|
|
tw@2970
|
12 |
2007 MoinMoin:ThomasWaldmann
|
|
tw-public@0
|
13 |
@license: GNU GPL, see COPYING for details.
|
|
tw-public@0
|
14 |
"""
|
|
tw-public@0
|
15 |
|
|
tw-public@0
|
16 |
_debug = 0
|
|
tw-public@0
|
17 |
|
|
tw@2970
|
18 |
import time
|
|
tw@2970
|
19 |
|
|
tw@1791
|
20 |
from MoinMoin import caching, wikiutil, logfile
|
|
tw-public@0
|
21 |
from MoinMoin.Page import Page
|
|
tw@749
|
22 |
from MoinMoin.logfile import eventlog
|
|
tw-public@0
|
23 |
|
|
tw@2970
|
24 |
# this is a CONSTANT used for on-disk caching, it must NOT be configurable and
|
|
tw@2970
|
25 |
# not depend on request.user!
|
|
tw@2970
|
26 |
DATE_FMT = '%04d-%02d-%02d' # % (y, m, d)
|
|
tw-public@0
|
27 |
|
|
tw-public@0
|
28 |
def linkto(pagename, request, params=''):
|
|
tw-public@0
|
29 |
_ = request.getText
|
|
tw-public@0
|
30 |
|
|
tw-public@0
|
31 |
if not request.cfg.chart_options:
|
|
tw-public@0
|
32 |
return text(pagename, request, params)
|
|
tw-public@0
|
33 |
|
|
tw-public@0
|
34 |
if _debug:
|
|
tw-public@0
|
35 |
return draw(pagename, request)
|
|
tw-public@0
|
36 |
|
|
tw-public@0
|
37 |
page = Page(request, pagename)
|
|
tw-public@0
|
38 |
|
|
tw-public@0
|
39 |
# Create escaped query string from dict and params
|
|
tw-public@0
|
40 |
querystr = {'action': 'chart', 'type': 'hitcounts'}
|
|
tw@102
|
41 |
querystr = wikiutil.makeQueryString(querystr)
|
|
tw-public@0
|
42 |
querystr = wikiutil.escape(querystr)
|
|
tw-public@0
|
43 |
if params:
|
|
tw-public@0
|
44 |
querystr += '&' + params
|
|
tw@995
|
45 |
|
|
tw@1816
|
46 |
data = {'url': page.url(request, querystr)}
|
|
tw-public@0
|
47 |
data.update(request.cfg.chart_options)
|
|
tw-public@0
|
48 |
result = ('<img src="%(url)s" width="%(width)d" height="%(height)d"'
|
|
tw-public@0
|
49 |
' alt="hitcounts chart">') % data
|
|
tw-public@0
|
50 |
|
|
tw-public@0
|
51 |
return result
|
|
tw-public@0
|
52 |
|
|
tw-public@0
|
53 |
|
|
tw-public@0
|
54 |
def get_data(pagename, request, filterpage=None):
|
|
tw@1017
|
55 |
cache_days, cache_views, cache_edits = [], [], []
|
|
tw@1017
|
56 |
cache_date = 0
|
|
tw@995
|
57 |
|
|
tw-public@0
|
58 |
# Get results from cache
|
|
tw-public@0
|
59 |
if filterpage:
|
|
tw-public@0
|
60 |
arena = Page(request, pagename)
|
|
tw@2970
|
61 |
cache = caching.CacheEntry(request, arena, 'hitcounts', scope='item', use_pickle=True)
|
|
tw-public@0
|
62 |
else:
|
|
tw-public@0
|
63 |
arena = 'charts'
|
|
tw@2970
|
64 |
cache = caching.CacheEntry(request, arena, 'hitcounts', scope='wiki', use_pickle=True)
|
|
tw@995
|
65 |
|
|
tw-public@0
|
66 |
if cache.exists():
|
|
tw-public@0
|
67 |
try:
|
|
tw@2970
|
68 |
cache_date, cache_days, cache_views, cache_edits = cache.content()
|
|
tw-public@0
|
69 |
except:
|
|
tw-public@0
|
70 |
cache.remove() # cache gone bad
|
|
tw-public@0
|
71 |
|
|
tw-public@0
|
72 |
# Get new results from the log
|
|
tw-public@0
|
73 |
log = eventlog.EventLog(request)
|
|
tw-public@0
|
74 |
try:
|
|
tw-public@0
|
75 |
new_date = log.date()
|
|
tw-public@0
|
76 |
except logfile.LogMissing:
|
|
tw-public@0
|
77 |
new_date = None
|
|
tw@995
|
78 |
|
|
tw-public@0
|
79 |
# prepare data
|
|
tw-public@0
|
80 |
days = []
|
|
tw-public@0
|
81 |
views = []
|
|
tw-public@0
|
82 |
edits = []
|
|
tw-public@0
|
83 |
ratchet_day = None
|
|
tw-public@0
|
84 |
ratchet_time = None
|
|
tw-public@0
|
85 |
if new_date is not None:
|
|
tw-public@0
|
86 |
log.set_filter(['VIEWPAGE', 'SAVEPAGE'])
|
|
MoinMoinBugs/ClickOnNavibarIncreasesHitsByMultiplicator)">rb@3469
|
87 |
latest = None
|
|
tw-public@0
|
88 |
for event in log.reverse():
|
|
MoinMoinBugs/ClickOnNavibarIncreasesHitsByMultiplicator)">rb@3469
|
89 |
# don't use event_log.date()
|
|
MoinMoinBugs/ClickOnNavibarIncreasesHitsByMultiplicator)">rb@3469
|
90 |
if latest is None:
|
|
MoinMoinBugs/ClickOnNavibarIncreasesHitsByMultiplicator)">rb@3469
|
91 |
latest = event[0]
|
|
tw@2970
|
92 |
event_usecs = event[0]
|
|
tw@2970
|
93 |
if event_usecs <= cache_date:
|
|
tw-public@0
|
94 |
break
|
|
tw@995
|
95 |
eventpage = event[2].get('pagename', '')
|
|
tw-public@0
|
96 |
if filterpage and eventpage != filterpage:
|
|
tw-public@0
|
97 |
continue
|
|
tw@2970
|
98 |
event_secs = wikiutil.version2timestamp(event_usecs)
|
|
tw@2970
|
99 |
time_tuple = time.gmtime(event_secs) # must be UTC
|
|
tw-public@0
|
100 |
day = tuple(time_tuple[0:3])
|
|
tw-public@0
|
101 |
if day != ratchet_day:
|
|
tw-public@0
|
102 |
# new day
|
|
tw-public@0
|
103 |
while ratchet_time:
|
|
tw@2970
|
104 |
ratchet_time -= 86400 # seconds per day
|
|
tw@2970
|
105 |
rday = tuple(time.gmtime(ratchet_time)[0:3]) # must be UTC
|
|
tw@1988
|
106 |
if rday <= day:
|
|
tw@1988
|
107 |
break
|
|
tw@2970
|
108 |
days.append(DATE_FMT % rday)
|
|
tw-public@0
|
109 |
views.append(0)
|
|
tw-public@0
|
110 |
edits.append(0)
|
|
tw@2970
|
111 |
days.append(DATE_FMT % day)
|
|
tw-public@0
|
112 |
views.append(0)
|
|
tw-public@0
|
113 |
edits.append(0)
|
|
tw-public@0
|
114 |
ratchet_day = day
|
|
tw@2970
|
115 |
ratchet_time = event_secs
|
|
tw-public@0
|
116 |
if event[1] == 'VIEWPAGE':
|
|
tw@2970
|
117 |
views[-1] += 1
|
|
tw-public@0
|
118 |
elif event[1] == 'SAVEPAGE':
|
|
tw@2970
|
119 |
edits[-1] += 1
|
|
tw@995
|
120 |
|
|
tw-public@0
|
121 |
days.reverse()
|
|
tw-public@0
|
122 |
views.reverse()
|
|
tw-public@0
|
123 |
edits.reverse()
|
|
tw-public@0
|
124 |
|
|
tw-public@0
|
125 |
# merge the day on the end of the cache
|
|
tw-public@0
|
126 |
if cache_days and days and days[0] == cache_days[-1]:
|
|
tw-public@0
|
127 |
cache_edits[-1] += edits[0]
|
|
tw-public@0
|
128 |
cache_views[-1] += views[0]
|
|
tw-public@0
|
129 |
days, views, edits = days[1:], views[1:], edits[1:]
|
|
tw-public@0
|
130 |
|
|
tw-public@0
|
131 |
# Update and save the cache
|
|
tw-public@0
|
132 |
cache_days.extend(days)
|
|
tw-public@0
|
133 |
cache_views.extend(views)
|
|
tw-public@0
|
134 |
cache_edits.extend(edits)
|
|
tw-public@0
|
135 |
if new_date is not None:
|
|
MoinMoinBugs/ClickOnNavibarIncreasesHitsByMultiplicator)">rb@3469
|
136 |
cache.update((latest, cache_days, cache_views, cache_edits))
|
|
tw-public@0
|
137 |
|
|
tw-public@0
|
138 |
return cache_days, cache_views, cache_edits
|
|
tw-public@0
|
139 |
|
|
tw-public@0
|
140 |
|
|
tw-public@0
|
141 |
def text(pagename, request, params=''):
|
|
tw-public@0
|
142 |
from MoinMoin.util.dataset import TupleDataset, Column
|
|
tw-public@0
|
143 |
from MoinMoin.widget.browser import DataBrowserWidget
|
|
tw-public@0
|
144 |
_ = request.getText
|
|
tw-public@0
|
145 |
|
|
tw-public@0
|
146 |
# check params
|
|
tw-public@0
|
147 |
filterpage = None
|
|
tw-public@0
|
148 |
if params.startswith('page='):
|
|
tw@4569
|
149 |
filterpage = wikiutil.url_unquote(params[len('page='):])
|
|
tw@995
|
150 |
|
|
tw@4426
|
151 |
if request and request.values and 'page' in request.values:
|
|
tw@4426
|
152 |
filterpage = request.values['page']
|
|
tw-public@0
|
153 |
|
|
tw-public@0
|
154 |
days, views, edits = get_data(pagename, request, filterpage)
|
|
tw-public@0
|
155 |
|
|
tw-public@0
|
156 |
hits = TupleDataset()
|
|
tw-public@0
|
157 |
hits.columns = [Column('day', label=_("Date"), align='left'),
|
|
tw@995
|
158 |
Column('views', label=_("Views/day"), align='right'),
|
|
tw@995
|
159 |
Column('edits', label=_("Edits/day"), align='right'),
|
|
tw-public@0
|
160 |
]
|
|
tw-public@0
|
161 |
|
|
tw-public@0
|
162 |
maxentries = 30
|
|
tw-public@0
|
163 |
|
|
tw-public@0
|
164 |
if maxentries < len(days):
|
|
tw-public@0
|
165 |
step = float(len(days))/ maxentries
|
|
tw-public@0
|
166 |
else:
|
|
tw-public@0
|
167 |
step = 1
|
|
tw@995
|
168 |
|
|
tw-public@0
|
169 |
sv = 0.0
|
|
tw-public@0
|
170 |
se = 0.0
|
|
tw-public@0
|
171 |
sd = 0.0
|
|
tw-public@0
|
172 |
cnt = 0
|
|
tw-public@0
|
173 |
|
|
tw@995
|
174 |
for i in xrange(len(days)-1, -1, -1):
|
|
tw@995
|
175 |
d, v, e = days[i], views[i], edits[i]
|
|
tw@2286
|
176 |
# sum up views and edits to step days
|
|
tw-public@0
|
177 |
sd += 1
|
|
tw-public@0
|
178 |
cnt += 1
|
|
tw-public@0
|
179 |
sv += v
|
|
tw-public@0
|
180 |
se += e
|
|
tw-public@0
|
181 |
if cnt >= step:
|
|
tw-public@0
|
182 |
cnt -= step
|
|
tw-public@0
|
183 |
hits.addRow((d, "%.1f" % (sv/sd), "%.1f" % (se/sd)))
|
|
tw-public@0
|
184 |
sv = 0.0
|
|
tw-public@0
|
185 |
se = 0.0
|
|
tw-public@0
|
186 |
sd = 0.0
|
|
tw-public@0
|
187 |
|
|
tw-public@0
|
188 |
table = DataBrowserWidget(request)
|
|
tw-public@0
|
189 |
table.setData(hits)
|
|
rb@4093
|
190 |
return table.render(method="GET")
|
|
tw-public@0
|
191 |
|
|
tw-public@0
|
192 |
|
|
tw-public@0
|
193 |
def draw(pagename, request):
|
|
tw-public@0
|
194 |
import shutil, cStringIO
|
|
tw-public@0
|
195 |
from MoinMoin.stats.chart import Chart, ChartData, Color
|
|
tw-public@0
|
196 |
|
|
tw-public@0
|
197 |
_ = request.getText
|
|
tw-public@0
|
198 |
|
|
tw-public@0
|
199 |
# check params
|
|
tw-public@0
|
200 |
filterpage = None
|
|
tw@4426
|
201 |
if request and request.values and 'page' in request.values:
|
|
tw@4426
|
202 |
filterpage = request.values['page']
|
|
tw-public@0
|
203 |
|
|
tw-public@0
|
204 |
days, views, edits = get_data(pagename, request, filterpage)
|
|
tw-public@0
|
205 |
|
|
tw-public@0
|
206 |
import math
|
|
tw@995
|
207 |
|
|
tw-public@0
|
208 |
try:
|
|
tw-public@0
|
209 |
scalefactor = float(max(views))/max(edits)
|
|
tw-public@0
|
210 |
except (ZeroDivisionError, ValueError):
|
|
tw-public@0
|
211 |
scalefactor = 1.0
|
|
tw-public@0
|
212 |
else:
|
|
tw-public@0
|
213 |
scalefactor = int(10 ** math.floor(math.log10(scalefactor)))
|
|
tw-public@0
|
214 |
|
|
tw@1866
|
215 |
# scale edits up
|
|
tw@1866
|
216 |
edits = [x * scalefactor for x in edits]
|
|
tw-public@0
|
217 |
|
|
tw-public@0
|
218 |
# create image
|
|
tw-public@0
|
219 |
image = cStringIO.StringIO()
|
|
tw-public@0
|
220 |
c = Chart()
|
|
tw-public@0
|
221 |
c.addData(ChartData(views, color='green'))
|
|
tw-public@0
|
222 |
c.addData(ChartData(edits, color='red'))
|
|
tw-public@0
|
223 |
chart_title = ''
|
|
tw@995
|
224 |
if request.cfg.sitename:
|
|
tw@995
|
225 |
chart_title = "%s: " % request.cfg.sitename
|
|
tw-public@0
|
226 |
chart_title = chart_title + _('Page hits and edits')
|
|
tw@995
|
227 |
if filterpage:
|
|
tw@995
|
228 |
chart_title = _("%(chart_title)s for %(filterpage)s") % {
|
|
tw@995
|
229 |
'chart_title': chart_title,
|
|
tw@995
|
230 |
'filterpage': filterpage,
|
|
tw@995
|
231 |
}
|
|
tw-public@0
|
232 |
chart_title = "%s\n%sx%d" % (chart_title, _("green=view\nred=edit"), scalefactor)
|
|
tw-public@0
|
233 |
c.option(
|
|
tw@995
|
234 |
title=chart_title.encode('iso-8859-1', 'replace'), # gdchart can't do utf-8
|
|
tw@995
|
235 |
xtitle=(_('date') + ' (Server)').encode('iso-8859-1', 'replace'),
|
|
tw@995
|
236 |
ytitle=_('# of hits').encode('iso-8859-1', 'replace'),
|
|
tw@995
|
237 |
title_font=c.GDC_GIANT,
|
|
tw-public@0
|
238 |
#thumblabel = 'THUMB', thumbnail = 1, thumbval = 10,
|
|
tw-public@0
|
239 |
#ytitle_color = Color('green'),
|
|
tw-public@0
|
240 |
#yaxis2 = 1,
|
|
tw-public@0
|
241 |
#ytitle2 = '# of edits',
|
|
tw-public@0
|
242 |
#ytitle2_color = Color('red'),
|
|
tw-public@0
|
243 |
#ylabel2_color = Color('black'),
|
|
tw-public@0
|
244 |
#interpolations = 0,
|
|
tw@995
|
245 |
threed_depth=1.0,
|
|
tw@995
|
246 |
requested_yinterval=1.0,
|
|
tw@995
|
247 |
stack_type=c.GDC_STACK_BESIDE
|
|
tw-public@0
|
248 |
)
|
|
tw-public@0
|
249 |
c.draw(c.GDC_LINE,
|
|
tw-public@0
|
250 |
(request.cfg.chart_options['width'], request.cfg.chart_options['height']),
|
|
tw-public@0
|
251 |
image, days)
|
|
tw-public@0
|
252 |
|
|
florian@4183
|
253 |
request.content_type = 'image/gif'
|
|
florian@4183
|
254 |
request.content_length = len(image.getvalue())
|
|
tw-public@0
|
255 |
|
|
tw-public@0
|
256 |
# copy the image
|
|
tw-public@0
|
257 |
image.reset()
|
|
tw-public@0
|
258 |
shutil.copyfileobj(image, request, 8192)
|
|
tw-public@0
|
259 |
|