Completed
Push — master ( 824653...e6f560 )
by Egor
01:03
created

get_users_live_versions()   F

Complexity

Conditions 9

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
c 1
b 0
f 0
dl 0
loc 32
rs 3
1
# coding: utf8
2
3
"""
4
This software is licensed under the Apache 2 license, quoted below.
5
6
Copyright 2014 Crystalnix Limited
7
8
Licensed under the Apache License, Version 2.0 (the "License"); you may not
9
use this file except in compliance with the License. You may obtain a copy of
10
the License at
11
12
    http://www.apache.org/licenses/LICENSE-2.0
13
14
Unless required by applicable law or agreed to in writing, software
15
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17
License for the specific language governing permissions and limitations under
18
the License.
19
"""
20
21
import os
22
23
from builtins import range
24
25
from functools import partial
26
from datetime import datetime, timedelta
27
28
from django.conf import settings
29
from django.db import transaction
30
from django.utils import timezone
31
from bitmapist import (
32
    setup_redis, mark_event, unmark_event,
33
    WeekEvents, MonthEvents, DayEvents, HourEvents
34
)
35
import pytz
36
37
from omaha.utils import (
38
    get_id, is_new_install, valuedispatch,
39
    redis, get_platforms_by_appid
40
)
41
from omaha import parser
42
from omaha.models import (
43
    ACTIVE_USERS_DICT_CHOICES, Request, AppRequest,
44
    Os, Hw, Event, Version, Channel, Platform
45
)
46
from sparkle.models import SparkleVersion
47
48
__all__ = ['userid_counting', 'is_user_active']
49
50
setup_redis('default', 
51
            settings.REDIS_STAT_HOST,
52
            settings.REDIS_STAT_PORT, 
53
            db=settings.REDIS_STAT_DB, 
54
            password=settings.REDIS_PASSWORD)
55
56
57
def userid_counting(userid, apps_list, platform, now=None):
58
    id = get_id(userid)
59
    mark_event('request', id, now=now)
60
    list(map(partial(add_app_statistics, id, platform, now=now), apps_list or []))
61
62
63
def add_app_statistics(userid, platform, app, now=None):
64
    mark = partial(mark_event, now=now)
65
    if not now:
66
        now = timezone.now()
67
    appid = app.get('appid')
68
    version = app.get('version')
69
    channel = parser.get_channel(app)
70
    events = app.findall('event')
71
    nextversion = app.get('nextversion')
72
73
    err_events = filter(lambda x: x.get('eventresult') not in ['1', '2', '3'], events)
74
    if err_events:
75
        return
76
77
    install_event = filter(lambda x: x.get('eventtype') == '2', events)
78
    if is_new_install(appid, userid):
79
        if install_event:
80
            mark('new_install:%s' % appid, userid)
81
            mark('new_install:{}:{}'.format(appid, platform), userid)
82
            redis.setbit("known_users:%s" % appid, userid, 1)
83
            mark('request:{}:{}'.format(appid, nextversion), userid, track_hourly=True)
84
            mark('request:{}:{}:{}'.format(appid, platform, nextversion), userid, track_hourly=True)
85
            mark('request:{}:{}:{}:{}'.format(appid, platform, channel, nextversion), userid, track_hourly=True)
86
            mark('request:{}:{}'.format(appid, channel), userid)
87
            return
88
89
    elif userid not in MonthEvents('new_install:{}:{}'.format(appid, platform), year=now.year, month=now.month):
90
        mark('request:%s' % appid, userid)
91
        mark('request:{}:{}'.format(appid, platform), userid)
92
        if nextversion:
93
            mark('request:{}:{}'.format(appid, nextversion), userid, track_hourly=True)
94
            mark('request:{}:{}:{}'.format(appid, platform, nextversion), userid, track_hourly=True)
95
            mark('request:{}:{}:{}:{}'.format(appid, platform, channel, nextversion), userid, track_hourly=True)
96
97
    uninstall_event = filter(lambda x: x.get('eventtype') == '4', events)
98
    if uninstall_event:
99
        mark('uninstall:%s' % appid, userid)
100
        mark('uninstall:{}:{}'.format(appid, platform), userid)
101
    update_event = filter(lambda x: x.get('eventtype') == '3', events)
102
    if update_event:
103
        unmark_event('request:{}:{}'.format(appid, version), userid, track_hourly=True)
104
        unmark_event('request:{}:{}:{}'.format(appid, platform, version), userid, track_hourly=True)
105
        unmark_event('request:{}:{}:{}:{}'.format(appid, platform, channel, version), userid, track_hourly=True)
106
        mark('request:{}:{}'.format(appid, nextversion), userid, track_hourly=True)
107
        mark('request:{}:{}:{}'.format(appid, platform, nextversion), userid, track_hourly=True)
108
        mark('request:{}:{}:{}:{}'.format(appid, platform, channel, nextversion), userid, track_hourly=True)
109
    else:
110
        mark('request:{}:{}'.format(appid, version), userid, track_hourly=True)
111
        mark('request:{}:{}:{}'.format(appid, platform, version), userid, track_hourly=True)
112
        mark('request:{}:{}:{}:{}'.format(appid, platform, channel, version), userid, track_hourly=True)
113
    mark('request:{}:{}'.format(appid, channel), userid)
114
115
116
def get_users_statistics_months(app_id, platform=None, year=None, start=1, end=12):
117
    now = timezone.now()
118
    if not year:
119
        year = now.year
120
121
    if platform:
122
        install_event_name = 'new_install:{}:{}'.format(app_id, platform)
123
        update_event_name = 'request:{}:{}'.format(app_id, platform)
124
        uninstall_event_name = 'uninstall:{}:{}'.format(app_id, platform)
125
    else:
126
        install_event_name = 'new_install:%s' % app_id
127
        update_event_name = 'request:%s' % app_id
128
        uninstall_event_name = 'uninstall:%s' % app_id
129
130
    installs_by_month = []
131
    updates_by_month = []
132
    uninstalls_by_month = []
133
    for m in range(start, end + 1):
134
        installs_by_month.append(MonthEvents(install_event_name, year, m))
135
        updates_by_month.append(MonthEvents(update_event_name, year, m))
136
        uninstalls_by_month.append(MonthEvents(uninstall_event_name, year, m))
137
    installs_data = [(datetime(year, start + i, 1).strftime("%Y-%m"), len(e)) for i, e in enumerate(installs_by_month)]
138
    updates_data = [(datetime(year, start + i, 1).strftime("%Y-%m"), len(e)) for i, e in enumerate(updates_by_month)]
139
    res = dict(new=installs_data, updates=updates_data)
140
    if platform != 'mac':
141
        uninstalls_data = [(datetime(year, start + i, 1).strftime("%Y-%m"), len(e)) for i, e in enumerate(uninstalls_by_month)]
142
        res.update(dict(uninstalls=uninstalls_data))
143
    return res
144
145
146
def get_users_statistics_weeks(app_id=None):
147
    now = timezone.now()
148
    event_name = 'request:%s' % app_id if app_id else 'request'
149
    year = now.year
150
    current_week = now.isocalendar()[1]
151
    previous_week = (now - timedelta(weeks=1)).isocalendar()[1]
152
    yesterday = now - timedelta(days=1)
153
    data = [
154
        ('Previous week', len(WeekEvents(event_name, year, previous_week))),
155
        ('Current week', len(WeekEvents(event_name, year, current_week))),
156
        ('Yesterday', len(DayEvents(event_name, year, yesterday.month, yesterday.day))),
157
        ('Today', len(DayEvents(event_name, year, now.month, now.day))),
158
    ]
159
    return data
160
161
162
def get_channel_statistics(app_id, date=None):
163
    if not date:
164
        date = timezone.now()
165
166
    event_name = 'request:{}:{}'
167
    channels = [c.name for c in Channel.objects.all()]
168
    data = [(channel, len(MonthEvents(event_name.format(app_id, channel), date.year, date.month))) for channel in channels]
169
    data = filter(lambda x: x[1], data)
170
    return data
171
172
173
def get_users_versions_by_platform(app_id, platform, date):
174
    if platform == 'mac':
175
        versions = [str(v) for v in SparkleVersion.objects.filter_by_enabled(app_id=app_id).values_list('short_version', flat=True)]
176
    else:
177
        versions = [str(v) for v in Version.objects.filter_by_enabled(app__id=app_id, platform__name=platform).values_list('version', flat=True)]
178
    event_name = 'request:{}:{}:{}'
179
    data = [(v, len(MonthEvents(event_name.format(app_id, platform, v), date.year, date.month))) for v in versions]
180
    data = filter(lambda x: x[1], data)
181
    return dict(data)
182
183
184
def get_users_versions(app_id, date=None):
185 View Code Duplication
    if not date:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
186
        date = timezone.now()
187
188
    platforms = Platform.objects.values_list('name', flat=True)
189
    data = dict()                   # try to move it in the separate function
190
    for platform in platforms:
191
        platform_data = get_users_versions_by_platform(app_id, platform, date)
192
        data.update({platform: platform_data})
193
194
    return data
195
196
197
198
def get_hourly_data_by_platform(app_id, end, n_hours, versions, platform, channel, tz='UTC'):
199
    def build_event_name(app_id, platform, channel, v):
200
        if channel:
201
            return "request:{}:{}:{}:{}".format(app_id, platform, channel, v)
202
        else:
203
            return "request:{}:{}:{}".format(app_id, platform, v)
204
205
    tzinfo = pytz.timezone(tz)
206 View Code Duplication
    start = end - timezone.timedelta(hours=n_hours)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
207
208
    hours = [datetime(start.year, start.month, start.day, start.hour, tzinfo=pytz.UTC)
209
             + timezone.timedelta(hours=x) for x in range(1, n_hours + 1)]
210
211
    data = [(v, [[hour.astimezone(tzinfo).strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
212
                  len(HourEvents.from_date(build_event_name(app_id, platform, channel, v), hour))]
213
                 for hour in hours])
214
            for v in versions]
215
    data = filter(lambda version_data: sum([data[1] for data in version_data[1]]), data)
216
    return dict(data)
217
218
219
def get_daily_data_by_platform(app_id, end, n_days, versions, platform, channel):
220
    def build_event_name(app_id, platform, channel, v):
221
        if channel:
222
            return "request:{}:{}:{}:{}".format(app_id, platform, channel, v)
223
        else:
224
            return "request:{}:{}:{}".format(app_id, platform, v)
225
226
    start = end - timezone.timedelta(days=n_days)
227
228
    days = [start + timezone.timedelta(days=x) for x in range(0, n_days+1)]
229
    data = [(v, [[day.strftime("%Y-%m-%dT00:%M:%S.%fZ"),
230
                  len(DayEvents.from_date(build_event_name(app_id, platform, channel, v), day))]
231
                 for day in days])
232
            for v in versions]
233
    data = filter(lambda version_data: sum([data[1] for data in version_data[1]]), data)
234
    return dict(data)
235
236
237
def get_users_live_versions(app_id, start, end, channel, tz='UTC'):
238
    import logging
239
    logging.info("Getting active versions from DB")
240
    platforms = [x.name for x in get_platforms_by_appid(app_id)]
241
    versions = {}
242
243
    for platform in platforms:
244
        if platform == 'mac':
245
            platform_data = [str(v) for v in SparkleVersion.objects.filter_by_enabled(app_id=app_id).values_list('short_version', flat=True)]
246
        else:
247
            platform_data = [str(v) for v in Version.objects.filter_by_enabled(app__id=app_id, platform__name=platform).values_list('version', flat=True)]
248
        versions.update({platform: platform_data})
249
250
    logging.info("Getting statistics from Redis")
251
    if start < timezone.now() - timedelta(days=7):
252
        n_days = (end-start).days
253
254
        data = dict()
255
        for platform in platforms:
256
            platform_data = get_daily_data_by_platform(app_id, end, n_days, versions[platform], platform, channel)
257
            data.update({platform: platform_data})
258
259
    else:
260
        tmp_hours = divmod((end - start).total_seconds(), 60 * 60)
261
        n_hours = tmp_hours[0] + 1
262
        n_hours = int(n_hours)
263
264
        data = dict()
265
        for platform in platforms:
266
            platform_data = get_hourly_data_by_platform(app_id, end, n_hours, versions[platform], platform, channel, tz=tz)
267
            data.update({platform: platform_data})
268
    return data
269
270
271
@valuedispatch
272
def is_user_active(period, userid):
273
    return False
274
275
276
@is_user_active.register(ACTIVE_USERS_DICT_CHOICES['all'])
277
def _(period, userid):
278
    return True
279
280
281
@is_user_active.register(ACTIVE_USERS_DICT_CHOICES['week'])
282
def _(period, userid):
283
    return get_id(userid) in WeekEvents.from_date('request', timezone.now())
284
285
286
@is_user_active.register(ACTIVE_USERS_DICT_CHOICES['month'])
287
def _(period, userid):
288
    return get_id(userid) in MonthEvents.from_date('request', timezone.now())
289
290
291
def get_kwargs_for_model(cls, obj, exclude=None):
292
    exclude = exclude or []
293
    fields = [(field.name, field.to_python) for field in cls._meta.fields if field.name not in exclude]
294
    kwargs = dict([(i, convert(obj.get(i))) for (i, convert) in fields])
295
    return kwargs
296
297
298
def parse_os(os):
299
    kwargs = get_kwargs_for_model(Os, os, exclude=['id'])
300
    obj, flag = Os.objects.get_or_create(**kwargs)
301
    return obj
302
303
304
def parse_hw(hw):
305
    kwargs = get_kwargs_for_model(Hw, hw, exclude=['id'])
306
    obj, flag = Hw.objects.get_or_create(**kwargs)
307
    return obj
308
309
310
def parse_req(request, ip=None):
311
    kwargs = get_kwargs_for_model(Request, request, exclude=['os', 'hw', 'created', 'id'])
312
    kwargs['ip'] = ip
313
    return Request(**kwargs)
314
315
316
def parse_apps(apps, request):
317
    app_list = []
318
    for app in apps:
319
        events = app.findall('event')
320
321
        if not events:
322
            continue
323
324
        kwargs = get_kwargs_for_model(AppRequest, app, exclude=['request', 'version', 'nextversion', 'id'])
325
        kwargs['version'] = app.get('version') or None
326
        kwargs['nextversion'] = app.get('nextversion') or None
327
        kwargs['tag'] = parser.get_channel(app)
328
        app_req = AppRequest.objects.create(request=request, **kwargs)
329
        event_list = parse_events(events)
330
        app_req.events.add(*event_list)
331
        app_list.append(app_req)
332
    return app_list
333
334
335
def parse_events(events):
336
    res = []
337
    for event in events:
338
        kwargs = get_kwargs_for_model(Event, event)
339
        res.append(Event.objects.create(**kwargs))
340
    return res
341
342
343
@transaction.atomic
344
def collect_statistics(request, ip=None):
345
    userid = request.get('userid')
346
    apps = request.findall('app')
347
348
    if userid:
349
        userid_counting(userid, apps, request.os.get('platform'))
350
351
    if not filter(lambda app: bool(app.findall('event')), apps):
352
        return
353
354
    req = parse_req(request, ip)
355
    req.os = parse_os(request.os)
356
    req.hw = parse_hw(request.hw) if request.get('hw') else None
357
    req.save()
358
359
    parse_apps(apps, req)
360