redoing compat reports with es (bug 656527)

This commit is contained in:
Jeff Balogh 2011-06-17 12:06:42 -07:00
Родитель 8f6d83bb6c
Коммит a1635e2419
8 изменённых файлов: 170 добавлений и 128 удалений

Просмотреть файл

@ -1,62 +1,63 @@
import collections
import logging import logging
from django.conf import settings from django.conf import settings
from django.db.models import Sum, Max from django.db.models import Sum, Max
import cronjobs import cronjobs
import elasticutils
import redisutils import redisutils
import amo import amo
import amo.utils
import versions.compare as vc import versions.compare as vc
from addons.models import Addon from addons.models import Addon
from stats.models import UpdateCount from stats.models import UpdateCount
from .models import AppCompat
log = logging.getLogger('z.compat') log = logging.getLogger('z.compat')
@cronjobs.register @cronjobs.register
def compatibility_report(): def compatibility_report():
redis = redisutils.connections['master'] redis = redisutils.connections['master']
docs = collections.defaultdict(dict)
# for app in amo.APP_USAGE: # Gather all the data for the index.
for compat in settings.COMPAT: for app in amo.APP_USAGE:
app = amo.APPS_ALL[compat['app']] log.info(u'Making compat report for %s.' % app.pretty)
version = compat['version']
log.info(u'Making compat report for %s %s.' % (app.pretty, version))
versions = (('latest', version), ('beta', version + 'b'),
('alpha', compat['alpha']))
rv = dict((k, 0) for k in dict(versions))
rv['other'] = 0
ignore = (amo.STATUS_NULL, amo.STATUS_DISABLED)
qs = (Addon.objects.exclude(type=amo.ADDON_PERSONA, status__in=ignore)
.filter(appsupport__app=app.id, name__locale='en-us'))
latest = UpdateCount.objects.aggregate(d=Max('date'))['d'] latest = UpdateCount.objects.aggregate(d=Max('date'))['d']
qs = UpdateCount.objects.filter(addon__appsupport__app=app.id, qs = UpdateCount.objects.filter(addon__appsupport__app=app.id,
addon__status__in=amo.VALID_STATUSES,
addon___current_version__isnull=False,
date=latest) date=latest)
total = qs.aggregate(Sum('count'))['count__sum'] total = qs.aggregate(Sum('count'))['count__sum']
addons = list(qs.values_list('addon', 'count', 'addon__appsupport__min', redis.hset('compat:%s' % app.id, 'total', total)
'addon__appsupport__max'))
# Count up the top 95% of addons by ADU.
adus = 0 adus = 0
for addon, count, minver, maxver in addons:
# Don't count add-ons that weren't compatible with the previous updates = dict(qs.values_list('addon', 'count'))
# release for chunk in amo.utils.chunked(updates.items(), 50):
if maxver < vc.version_int(compat['previous']): chunk = dict(chunk)
continue for addon in Addon.objects.filter(id__in=chunk):
if adus < .95 * total: doc = docs[addon.id]
adus += count doc.update(id=addon.id, slug=addon.slug,
else: name=unicode(addon.name))
break doc.setdefault('usage', {})[app.id] = updates[addon.id]
for key, version in versions:
if minver <= vc.version_int(version) <= maxver: if app not in addon.compatible_apps:
rv[key] += 1 continue
break compat = addon.compatible_apps[app]
else: d = {'min': compat.min.version_int,
rv['other'] += 1 'max': compat.max.version_int}
log.info(u'Compat for %s %s: %s' % (app.pretty, version, rv)) doc.setdefault('support', {})[app.id] = d
key = '%s:%s' % (app.id, version) doc.setdefault('max_version', {})[app.id] = compat.max.version
redis.hmset('compat:' + key, rv) doc['top_95'] = adus > .95 * total
adus += sum(chunk.values())
# Send it all to the index.
for chunk in amo.utils.chunked(docs.values(), 150):
for doc in chunk:
AppCompat.index(doc, id=doc['id'], bulk=True)
elasticutils.get_es().flush_bulk(forced=True)

Просмотреть файл

@ -19,3 +19,20 @@ class CompatReport(amo.models.ModelBase):
class Meta: class Meta:
db_table = 'compatibility_reports' db_table = 'compatibility_reports'
class AppCompat(amo.models.ModelBase):
"""
Stub model for use with search. The schema:
{id: addon.id,
name: addon.name,
slug: addon.slug,
max_version: {APP.id: version string},
usage: {APP.id: addon.daily_usage},
support: {APP.id: {max: version int, min: version int},
}
"""
class Meta:
abstract = True

Просмотреть файл

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% set title = _('{app} {version} Add-on Compatibility Report')|f(app=request.APP.pretty, version=version) %}
{% block title %}{{ page_title(title) }}{% endblock %}
{% block bodyclass %}inverse{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
{% endblock %}

Просмотреть файл

@ -1,44 +1,62 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{{ page_title(_('Compatibility Center')) }}{% endblock %} {% set app = request.APP.pretty %}
{% set title = _('Add-on Compatibility Report for {app} {version}')|f(app=app, version=version) %}
{% block title %}{{ page_title(title) }}{% endblock %}
{% block bodyclass %}inverse{% endblock %} {% block bodyclass %}inverse{% endblock %}
{% macro percent(x, y) %}{{ (x / y|float * 100)|int }}{% endmacro %}
{% set app = request.APP.pretty %}
{% set num = percent(versions['latest'], total) %}
{% block content %} {% block content %}
<header>
<h2>{{ title }}</h2>
{{ breadcrumbs([(None, _('{app} {version} Compatibility')|f(app=app, version=version))]) }}
</header>
<h1>{{ _('{app} {version} Add-on Compatibility Report')|f(app=app, version=version) }}</h1> {% set titles = {
<p>{% trans %} 'prev': _('Top 95% compatible with previous version'),
<b>{{ num }}%</b> of add-ons on addons.mozilla.org are compatible with {{ app }} {{ version }}. 'top_95': _('Top 95% of all add-ons'),
{% endtrans %}</p> 'all': _('All active add-ons'),
} %}
<div id="chart" class="primary" <table>
data-keys="{{ dict(keys)|json }}" <thead>
data-data="{{ versions|json }}"
data-total="{{ total }}"></div>
<div class="secondary">
<table id="compat">
<tr> <tr>
<th>{{ _('Version') }}</th> <th></th>
{# L10n: "#" means "number" #} {% with key, (total, facets) = compat_levels[0] %}
<th>{{ _('# of Add-ons') }}</th> {% for version, _ in facets %}
<th>{{ version }}</th>
{% endfor %}
{% endwith %}
</tr> </tr>
{% for key, title in keys %} </thead>
<tbody>
{% for key, (total, facets) in compat_levels %}
{% if total %}
<tr> <tr>
<td>{{ title }}</td> <th>{{ titles[key] }}</th>
<td>{{ versions[key]|numberfmt }}</td> {% for version, count in facets %}
<td>{{ (100 * count / total)|round(2) }}% ({{ count }})</td>
{% endfor %}
</tr> </tr>
{% endif %}
{% endfor %} {% endfor %}
</table> </tbody>
<p><a href="{{ url('compat.details', version) }}">{{ _('Detailed Report') }}</a></p> </table>
</div> <ul>
</ul>
<h3>{{ _('Details for add-ons compatible with previous version:') }}</h3>
<table>
<tbody>
{% for addon in usage_addons.object_list %}
<tr>
<td><a href="{{ url('addons.detail', addon.slug) }}">{{ addon.name }}</a></td>
<td>{{ addon.max_version }}</td>
<td>{{ addon.usage|numberfmt }} users </td>
<td>({{ (100 * addon.usage / usage_total)|round(2) }}%)</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ usage_addons|paginator }}
{% endblock %} {% endblock %}
{% block js %}
<script src="{{ media('js/lib/highcharts.src.js') }}"></script>
<script src="{{ media('js/zamboni/compat.js') }}"></script>
{% endblock %}

Просмотреть файл

@ -10,5 +10,4 @@ urlpatterns = (
views.reporter_detail, name='compat.reporter_detail'), views.reporter_detail, name='compat.reporter_detail'),
url('^(?P<version>[.\w]+)?$', views.index, name='compat.index'), url('^(?P<version>[.\w]+)?$', views.index, name='compat.index'),
url('^(?P<version>[.\w]+)/details$', views.details, name='compat.details'),
) )

Просмотреть файл

@ -4,47 +4,73 @@ import re
from django import http from django import http
from django.conf import settings from django.conf import settings
from django.db.models import Count from django.db.models import Count
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
import jingo import jingo
import redisutils import redisutils
from tower import ugettext_lazy as _lazy from tower import ugettext as _
import amo.utils import amo.utils
from amo.decorators import post_required from amo.decorators import post_required
from addons.models import Addon from addons.models import Addon
from .models import CompatReport from versions.compare import version_int as vint
from .models import CompatReport, AppCompat
KEYS = (
('latest', _lazy('Latest')),
('beta', _lazy('Beta')),
('alpha', _lazy('Alpha')),
('other', _lazy('Other')),
)
def index(request, version=None): def index(request, version=None):
COMPAT = [v for v in settings.COMPAT if v['app'] == request.APP.id] COMPAT = [v for v in settings.COMPAT if v['app'] == request.APP.id]
if version is None and COMPAT: compat_dict = dict((v['main'], v) for v in COMPAT)
version = COMPAT[0]['version'] if not COMPAT:
redis = redisutils.connections['master']
compat = redis.hgetall('compat:%s:%s' % (request.APP.id, version))
versions = dict((k, int(v)) for k, v in compat.items())
if version not in [v['version'] for v in COMPAT] or not versions:
raise http.Http404() raise http.Http404()
if version not in compat_dict:
return redirect('compat.index', COMPAT[0]['main'])
total = sum(versions.values()) compat, app = compat_dict[version], str(request.APP.id)
keys = [(k, unicode(v)) for k, v in KEYS] qs = AppCompat.search()
compat_queries = (
('prev', qs.query(top_95=True, **{
'support.%s.max__gte' % app: vint(compat['previous'])})),
('top_95', qs.query(top_95=True)),
('all', qs),
)
compat_levels = [(key, version_compat(qs, compat, app))
for key, qs in compat_queries]
usage_addons, usage_total = usage_stats(request, compat, app)
return jingo.render(request, 'compat/index.html', return jingo.render(request, 'compat/index.html',
{'versions': versions, 'total': total, {'version': version,
'version': version, 'keys': keys}) 'usage_addons': usage_addons,
'usage_total': usage_total,
'compat_levels': compat_levels})
def details(request, version): def version_compat(qs, compat, app):
return http.HttpResponse('go away') facets = []
for v, prev in zip(compat['versions'], (None,) + compat['versions']):
d = {'from': vint(v)}
if prev:
d['to'] = vint(prev)
facets.append(d)
qs = qs.facet(by_status={'range': {'support.%s.max' % app: facets}})
result = qs[:0].raw()
total_addons = result['hits']['total']
ranges = result['facets']['by_status']['ranges']
faceted = [(v, r['count']) for v, r in zip(compat['versions'], ranges)]
other = total_addons - sum(r[1] for r in faceted)
return total_addons, faceted + [(_('Other'), other)]
def usage_stats(request, compat, app):
# Get the list of add-ons for usage stats.
redis = redisutils.connections['master']
qs = (AppCompat.search().order_by('-usage.%s' % app).values_dict()
.filter(**{'support.%s.max__gte' % app: vint(compat['previous'])}))
addons = amo.utils.paginate(request, qs)
for obj in addons.object_list:
obj['usage'] = obj['usage'][app]
obj['max_version'] = obj['max_version'][app]
total = int(redis.hget('compat:%s' % app, 'total'))
return addons, total
@csrf_exempt @csrf_exempt
@ -107,4 +133,3 @@ def reporter_detail(request, guid):
return jingo.render(request, 'compat/reporter_detail.html', return jingo.render(request, 'compat/reporter_detail.html',
dict(reports=reports, works=works, dict(reports=reports, works=works,
name=name, guid=guid)) name=name, guid=guid))

Просмотреть файл

@ -1,29 +0,0 @@
$(function() {
var chart = $('#chart'),
data = JSON.parse(chart.attr('data-data')),
total = chart.attr('data-total'),
keys = JSON.parse(chart.attr('data-keys')),
series = _.map(data, function(value, key) {
return [keys[key], parseInt(value / total * 100)];
});
console.log(JSON.stringify(series));
var chart = new Highcharts.Chart({
chart: {
renderTo: 'chart'
},
title: '',
plotOptions: {
pie: { cursor: 'pointer' }
},
tooltip: {
formatter: function() {
return '<b>'+ this.point.name +'</b>: '+ this.y +' %';
}
},
series: [{
type: 'pie',
data: series
}]
});
});

Просмотреть файл

@ -965,13 +965,13 @@ MODIFIED_DELAY = 3
# This is a list of dictionaries that we should generate compat info for. # This is a list of dictionaries that we should generate compat info for.
# app: should match amo.FIREFOX.id. # app: should match amo.FIREFOX.id.
# version: the app version we're generating compat info for. # main: the app version we're generating compat info for.
# alpha: the first version that should be considered alpha for :version. # versions: version numbers to show in comparisons.
# previous: the major version before :version. # previous: the major version before :main.
COMPAT = ( COMPAT = (
dict(app=1, version='4.0', alpha='3.7a', previous='3.6'), dict(app=1, main='6.0', versions=('6.0', '6.0a2', '6.0a1'), previous='5.0'),
dict(app=1, version='5.0', alpha='5.0a', previous='4.0'), dict(app=1, main='5.0', versions=('5.0', '5.0a2', '5.0a1'), previous='4.0'),
dict(app=1, version='6.0', alpha='6.0a', previous='5.0'), dict(app=1, main='4.0', versions=('4.0', '4.0a1', '3.7a'), previous='3.6'),
) )
# URL for reporting arecibo errors too. If not set, won't be sent. # URL for reporting arecibo errors too. If not set, won't be sent.