MoinMoin/stats/hitcounts.py
author Thomas Waldmann <tw AT waldmann-edv DOT de>
Wed, 11 Feb 2009 02:34:33 +0100
changeset 4569 3caaa8c74c41
parent 4426 8982d8226218
permissions -rw-r--r--
wikiutil: replace moin's cgi/urllib wrappers by calls to werkzeug.utils code
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 += '&amp;' + 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