Import user-report server side code

This was SVN commit r8986.
This commit is contained in:
Ykkrosh 2011-02-25 19:46:01 +00:00
parent 4e8c305c6e
commit f0ea32cb8d
28 changed files with 1593 additions and 0 deletions

View File

View File

@ -0,0 +1,11 @@
#!/usr/bin/env python
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
if __name__ == "__main__":
execute_manager(settings)

View File

@ -0,0 +1,120 @@
from settings_local import DEBUG, ADMINS, DATABASES, SECRET_KEY, WFGSITE_ROOT
TEMPLATE_DEBUG = DEBUG
MANAGERS = ADMINS
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# On Unix systems, a value of None will cause Django to use the same
# timezone as the operating system.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'UTC'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = False
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale
USE_L10N = False
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = ''
# URL that handles the static files served from STATIC_ROOT.
# Example: "http://static.lawrence.com/", "http://example.com/static/"
STATIC_URL = '/static/'
# URL prefix for admin media -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/static/admin/'
# A list of locations of additional static files
STATICFILES_DIRS = ()
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
ROOT_URLCONF = 'urls'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
'%s/userreport/templates' % WFGSITE_ROOT,
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'userreport',
)
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
#LOGGING = {
# 'version': 1,
# 'disable_existing_loggers': False,
# 'handlers': {
# 'mail_admins': {
# 'level': 'ERROR',
# 'class': 'django.utils.log.AdminEmailHandler'
# }
# },
# 'loggers': {
# 'django.request':{
# 'handlers': ['mail_admins'],
# 'level': 'ERROR',
# 'propagate': True,
# },
# }
#}

View File

@ -0,0 +1,20 @@
# Fill in this file and save as settings_local.py
DEBUG = True
ADMINS = (
('Your Name', 'you@example.com'),
)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'wfgsite',
'USER': 'wfgsite',
'PASSWORD': '################',
}
}
SECRET_KEY = '##################################################'
WFGSITE_ROOT = '/var/####/wfgsite'

View File

@ -0,0 +1,28 @@
This has been tested with Django 1.3 Beta 1.
To set up the database, run commands like:
In MySQL:
create database wfgservices character set utf8;
grant create,alter,index,select,insert,update,delete on wfgservices.* to 'wfgservices'@'localhost' identified by '[PASSWORD]';
In shell:
python manage.py syncdb
Copy settings_local.EXAMPLE.py to settings_local.py and fill in the details.
Set up Apache with some options kind of like:
WSGIScriptAlias / /var/www/wherever/django.wsgi
<Location />
SetOutputFilter DEFLATE
Options None
</Location>
<Location /private>
AuthType Basic
AuthName "WFG login"
AuthUserFile ...
Require valid-user
</Location>
Alias /static/admin/ /usr/lib/python2.5/site-packages/django/contrib/admin/media/
Alias /robots.txt /var/www/wherever/robots.txt
Alias /favicon.ico /var/www/wherever/favicon.ico

View File

@ -0,0 +1,11 @@
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^$', 'userreport.views.index'),
(r'^report/', include('userreport.urls')),
(r'^private/', include('userreport.urls_private')),
(r'^admin/', include(admin.site.urls)),
)

View File

@ -0,0 +1,17 @@
from userreport.models import UserReport
from django.contrib import admin
class UserReportAdmin(admin.ModelAdmin):
readonly_fields = ['uploader', 'user_id_hash', 'upload_date', 'generation_date', 'data_type', 'data_version', 'data']
fieldsets = [
('User', {'fields': ['uploader', 'user_id_hash']}),
('Dates', {'fields': ['upload_date', 'generation_date']}),
(None, {'fields': ['data_type', 'data_version', 'data']}),
]
list_display = ('uploader', 'user_id_hash', 'data_type', 'data_version', 'upload_date', 'generation_date')
list_filter = ['upload_date', 'generation_date', 'data_type']
search_fields = ['=uploader', '=user_id_hash']
date_hierarchy = 'upload_date'
admin.site.register(UserReport, UserReportAdmin)

View File

@ -0,0 +1,69 @@
from django.db import models
from django.utils import simplejson
class UserReport(models.Model):
uploader = models.IPAddressField(editable = False)
# Hex SHA-1 digest of user's reported ID
# (The hashing means that publishing the database won't let people upload
# faked reports under someone else's user ID, and also ensures a simple
# consistent structure)
user_id_hash = models.CharField(max_length = 40, db_index = True, editable = False)
# When the server received the upload
upload_date = models.DateTimeField(auto_now_add = True, db_index = True, editable = False)
# When the user claims to have generated the report
generation_date = models.DateTimeField(editable = False)
data_type = models.CharField(max_length = 16, db_index = True, editable = False)
data_version = models.IntegerField(editable = False)
data = models.TextField(editable = False)
def data_json(self):
if not hasattr(self, 'cached_json'):
try:
self.cached_json = simplejson.loads(self.data)
except:
self.cached_json = None
return self.cached_json
class UserReport_hwdetect(UserReport):
class Meta:
proxy = True
def os(self):
os = 'Unknown'
json = self.data_json()
if json:
if json['os_win']:
os = 'Windows'
elif json['os_linux']:
os = 'Linux'
elif json['os_macosx']:
os = 'OS X'
return os
def gl_extensions(self):
json = self.data_json()
if json is None or 'GL_EXTENSIONS' not in json:
return None
return frozenset(json['GL_EXTENSIONS'].strip().split(' '))
def gl_limits(self):
json = self.data_json()
if json is None:
return None
limits = {}
for (k, v) in json.items():
if not k.startswith('GL_'):
continue
if k in ('GL_RENDERER', 'GL_VENDOR', 'GL_EXTENSIONS'):
continue
limits[k] = v
return limits

View File

@ -0,0 +1,4 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>404</title>
File not found.

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>0 A.D. report service</title>
<style>
body {
font-size: 12px;
font-family: sans-serif;
}
</style>
<p>This site collects opt-in automatic feedback from players of <a href="http://wildfiregames.com/0ad/">0 A.D.</a>
<p>Published data:
<a href="{% url userreport.views.report_opengl_index %}">OpenGL capabilities</a>.
<a href="{% url userreport.views.report_cpu %}">CPU features</a>.

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>{% block title %}Report{% endblock %}</title>
<style>
body {
font-size: 12px;
font-family: sans-serif;
}
table {
border-collapse: collapse;
border-color: #ccc;
}
td, th {
line-height: 13px;
border-bottom: 1px solid #eee;
padding: 5px;
}
td {
font-size: 11px;
line-height: 13px;
border-bottom: 1px solid #eee;
vertical-align: top;
}
th {
font-size: 12px;
font-weight: bold;
vertical-align: bottom;
text-align: left;
}
tr.alt {
background: #f6f6f6;
}
{% block css %}{% endblock %}
</style>
<h1>{% block heading %}Report{% endblock %}</h1>
{% block content %}{% endblock %}
{% if report_page %}{% if report_page.has_previous or report_page.has_next %}
<div class="pagination">
<span class="step-links">
{% if report_page.has_previous %}
<a href="?page={{ report_page.previous_page_number }}" rel="prev">[previous]</a>
{% endif %}
<span class="current">
Page {{ report_page.number }} of {{ report_page.paginator.num_pages }}
</span>
{% if report_page.has_next %}
<a href="?page={{ report_page.next_page_number }}" rel="next">[next]</a>
{% endif %}
</span>
</div>
{% endif %}{% endif %}

View File

@ -0,0 +1,52 @@
{% extends "reports/base.html" %}
{% load report_tags %}
{% block css %}
table.profile td {
line-height: inherit;
border-bottom: inherit;
padding: 0 1em 0 0.5em;
}
.treemarker {
color: #666;
font-family: monospace;
white-space: pre;
}
{% endblock %}
{% block title %}
CPU capabilities report
{% endblock %}
{% block heading %}
CPU capabilities
{% endblock %}
{% block content %}
<p>Based on data submitted by players of <a href="http://wildfiregames.com/0ad/">0 A.D.</a></p>
<p>See the <a href="{% url userreport.views.index %}">index page</a> for more stuff.</p>
<table>
<tr>
<th>OS
<th>Identifier
<th><abbr title="Vendor/Model/Family">V/M/F</abbr>
<th>Freq
<th><abbr title="Num packages &times; cores per package &times; logical processors per core = number of processors">Num procs</abbr>
<th>Caches (data/instruction/unified)
<th>TLBs
<th>Feature bits
{% for cpu,users in cpus|sortedcpuitems %}
<tr {% cycle 'class=alt' '' %}>
<td>{{ cpu.os }}
<td><nobr>{{ cpu.cpu_identifier }}</nobr>
<td>{{ cpu.x86_vendor }}/{{ cpu.x86_model }}/{{ cpu.x86_family }}
<td>{% if cpu.cpu_frequency = -1 %}?{% else %}{{ cpu.cpu_frequency|cpufreqformat }}{% endif %}
<td>{{ cpu.cpu_numpackages }}&times;{{ cpu.cpu_coresperpackage }}&times;{{ cpu.cpu_logicalpercore }}&nbsp;=&nbsp;{{ cpu.cpu_numprocs }}
<td><nobr>{{ cpu.caches|join:"<br>" }}</nobr>
<td><nobr>{{ cpu.tlbs|join:"<br>" }}</nobr>
<td>{% for cap in cpu.caps|sort %}{% if cap in x86_cap_descs %}<abbr title="{{ x86_cap_descs|dictget:cap }}">{{ cap }}</abbr>{% else %}{{ cap }}{% endif %} {% endfor %}
{% endfor %}
</table>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends "reports/base.html" %}
{% load report_tags %}
{% block content %}
<table>
<tr>
<th>Received
<th>User
<th>Data
{% for report in report_page.object_list %}
<tr {% cycle 'class=alt' '' %}>
<td>{{ report.upload_date|date:"Y-m-d" }}&nbsp;{{ report.upload_date|date:"H:i:s" }}
<td><a href="{% url userreport.views_private.report_user report.user_id_hash %}"><abbr title="{{ report.user_id_hash }}">{{ report.user_id_hash|slice:"0:8" }}</abbr></a>
<td style="white-space: pre">{{ report.data|prettify_json }}
{% endfor %}
</table>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "reports/base.html" %}
{% block content %}
<table>
<tr>
<th>Received
<th>User
<th>Message
{% for report in report_page.object_list %}
<tr {% cycle 'class=alt' '' %}>
<td>{{ report.upload_date|date:"Y-m-d" }}&nbsp;{{ report.upload_date|date:"H:i:s" }}
<td><a href="{% url userreport.views_private.report_user report.user_id_hash %}"><abbr title="{{ report.user_id_hash }}">{{ report.user_id_hash|slice:"0:8" }}</abbr></a>
<td>{{ report.data }}
{% endfor %}
</table>
{% endblock %}

View File

@ -0,0 +1,94 @@
{% extends "reports/base.html" %}
{% load report_tags %}
{% load cycle %}
{% block css %}
th.alt, td.alt { background: #f6f6f6; }
td.true { background: #3f3; }
td.false { background: #f33; }
td.true.alt { background: #2e2; }
td.false.alt { background: #e22; }
.device-status ul {
margin: 0;
padding: 0;
}
.device-status li {
list-style: none;
}
{% endblock %}
{% block title %}
OpenGL capabilities report: {{ selected|join:' vs. ' }}
{% endblock %}
{% block heading %}
OpenGL capabilities report
{% endblock %}
{% block content %}
<p>(<a href="{% url userreport.views.report_opengl_index %}">Back to index page.</a>)</p>
<p>The table here shows the features reported for the following devices:</p>
<ul>
{% for device in selected %}<li>{{ device }}{% endfor %}
</ul>
<p>Different driver versions may have different feature sets,
and we may have conflicting reports from the same driver version.
There is a column for each distinct set of reported features.</p>
<p>Green cells indicate supported extensions; red cells indicate non-supported extensions.</p>
<table class=device-status>
<tr>
<td>
{% for ext in all_exts %}
{% if not forloop.counter0|mod:30 %}
<tr><td>
{% for device in devices %}
<th{% safe_cycle ' class=alt' '' %}>
{{ device.0.renderer }} ({{ device.0.os }}):
<ul>{% for driver in device.1 %}<li>{{ driver }}{% endfor %}</ul>
{% endfor %}
{% endif %}
<tr title="{{ ext }}">
<th><nobr><a href="{% url userreport.views.report_opengl_feature ext %}">{{ ext }}</a> <a href="{{ ext|glext_spec_link }}">(spec)</a></nobr>
{% for device in devices %}
<td class="{% if ext in device.2.1 %}true{% else %}false{% endif %}{% safe_cycle ' alt' '' %}">
{% endfor %}
{% endfor %}
{% for limit in all_limits %}
{% if not forloop.counter|mod:30 %}
<tr><td>
{% for device in devices %}
<th{% safe_cycle ' class=alt' '' %}>
{{ device.0.renderer }} ({{ device.0.os }}):
<ul>{% for driver in device.1 %}<li>{{ driver }}{% endfor %}</ul>
{% endfor %}
{% endif %}
<tr title="{{ limit }}">
<th><nobr><a href="{% url userreport.views.report_opengl_feature limit %}">{{ limit|prettify_gl_title }}</a></nobr>
{% for device in devices %}
<td{% safe_cycle ' class=alt' '' %}>{{ device.2.0|dictget:limit }}
{% endfor %}
{% endfor %}
</table>
<h2>Compare with other devices</h2>
<form action="{% url userreport.views.report_opengl_device_compare %}" method=get>
<ul>
{% for d in all_devices|sort %}
<li><label><input name=d value="{{ d }}" type=checkbox{% if d in selected %} checked{% endif %}> {{ d }}</label>
{% endfor %}
</ul>
<input type=submit value="Compare selected devices">
</form>
{% endblock %}

View File

@ -0,0 +1,121 @@
{% extends "reports/base.html" %}
{% load report_tags %}
{% load cycle %}
{% block css %}
ul {
margin: 0;
}
.support-status .r {
font-weight: bold;
}
.support-status ul {
margin: 0;
padding: 0;
}
.support-status li {
list-style: none;
}
tr.head {
background: #ddd;
}
tr.data:hover {
background: #ddd;
}
{% endblock %}
{% block title %}
OpenGL capabilities report: {{ feature }}
{% endblock %}
{% block heading %}
OpenGL capabilities report: {{ feature }}
{% endblock %}
{% block content %}
<p>(<a href="{% url userreport.views.report_opengl_index %}">Back to index page.</a>)</p>
{% if is_extension %}
<p><a href="{{ feature|glext_spec_link }}">View specification for {{ feature }}.</a></p>
<table class=support-status>
<tr>
<td colspan=4><h2>Supported by:</h2>
<tr class=head>
<th>Vendor
<th>Renderer
<th>OS
<th>Driver versions
{% for device in values.true|sorteddeviceitems %}
<tr class="data{% safe_cycle '' ' alt'%}">
<td>{{ device.0.vendor }}
<td class=r><a href="{% url userreport.views.report_opengl_device device.0.renderer %}">{{ device.0.renderer }}</a>
<td>{{ device.0.os }}
<td><ul>
{% for driver in device.1|sort %}<li>{{ driver }}{% endfor %}
</ul>
{% endfor %}
<tr>
<td colspan=4><h2>Not supported by:</h2>
<tr class=head>
<th>Vendor
<th>Renderer
<th>OS
<th>Driver versions
{% for device in values.false|sorteddeviceitems %}
<tr class="data{% safe_cycle '' ' alt'%}">
<td>{{ device.0.vendor }}
<td class=r><a href="{% url userreport.views.report_opengl_device device.0.renderer %}">{{ device.0.renderer }}</a>
<td>{{ device.0.os }}
<td><ul>
{% for driver in device.1|sort %}<li>{{ driver }}{% endfor %}
</ul>
{% endfor %}
</table>
{% else %}
<table class=support-status>
{% for val in values.keys|sortreversed %}
<tr>
<td colspan=4><h2>Value: {{ val|default_if_none:"Unsupported/unknown" }}</h2>
<tr class=head>
<th>Vendor
<th>Renderer
<th>OS
<th>Driver versions
{% for device in values|dictget:val|sorteddeviceitems %}
<tr class="data{% safe_cycle '' ' alt'%}">
<td>{{ device.0.vendor }}
<td class=r><a href="{% url userreport.views.report_opengl_device device.0.renderer %}">{{ device.0.renderer }}</a>
<td>{{ device.0.os }}
<td><ul>
{% for driver in device.1|sort %}<li>{{ driver }}{% endfor %}
</ul>
{% endfor %}
{% endfor %}
</table>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,102 @@
{% extends "reports/base.html" %}
{% load report_tags %}
{% block css %}
.col {
float: left;
}
.progress {
font-size: 10px;
font-weight: bold;
display: inline-block;
background: #f99;
width: 40px;
height: 13px;
}
.progress .bar {
display: inline-block;
position: absolute;
background: #3f3;
height: 13px;
z-index: 0;
}
li:hover {
background: #ddd;
}
{% endblock %}
{% block title %}
OpenGL capabilities database - index
{% endblock %}
{% block heading %}
OpenGL capabilities database
{% endblock %}
{% block content %}
<script>
function sort_by_percent(ul)
{
var data = [];
var lis = ul.getElementsByTagName('li');
for (var i = 0; i < lis.length; ++i)
data.push([+lis[i].getAttribute('data-supportedby'), lis[i].getAttribute('data-extname'), lis[i]]);
data.sort(function (a, b) { return b[0] - a[0] || (a[1] < b[1] ? -1 : b[1] < a[1] ? 1 : 0) });
for (var i = 0; i < data.length; ++i)
ul.appendChild(data[i][2]);
}
function sort_by_name(ul)
{
var data = [];
var lis = ul.getElementsByTagName('li');
for (var i = 0; i < lis.length; ++i)
data.push([lis[i].getAttribute('data-extname'), lis[i]]);
data.sort(function (a, b) { return (a[0] < b[0] ? -1 : b[0] < a[0] ? 1 : 0) });
for (var i = 0; i < data.length; ++i)
ul.appendChild(data[i][1]);
}
</script>
<p>Based on data submitted by players of <a href="http://wildfiregames.com/0ad/">0 A.D.</a>
<p>Browse the data here, or download as <a href="{% url userreport.views.report_opengl_json %}">JSON</a>.
Feel free to do whatever you want with the data.</p>
<p>See the <a href="{% url userreport.views.index %}">index page</a> for more stuff.</p>
<div class=col>
<h2>Extension support</h2>
Sort by
<a href="#" onclick="sort_by_percent(document.getElementById('extensions')); return false">% support</a> /
<a href="#" onclick="sort_by_name(document.getElementById('extensions')); return false">name</a>.
<ul id=extensions>
{% for ext in all_exts %}
<li data-supportedby={{ ext_devices|dictget:ext|length }} data-extname="{{ ext }}">
<span class=progress title="Percentage of devices that support this extension"><span class=bar style="width:{% widthratio ext_devices|dictget:ext|length all_devices|length 40 %}px">{% widthratio ext_devices|dictget:ext|length all_devices|length 100 %}%</span></span>
(<a href="{{ ext|glext_spec_link }}">spec</a>)
<a href="{% url userreport.views.report_opengl_feature ext %}">{{ ext }}</a>
{% endfor %}
</ul>
<h2>Implementation limits</h2>
<ul>
{% for limit in all_limits %}
<li><a href="{% url userreport.views.report_opengl_feature limit %}">{{ limit }}</a>
{% endfor %}
</ul>
</div>
<div class=col>
<h2>Device details</h2>
<ul>
{% for device in all_devices %}
<li><a href="{% url userreport.views.report_opengl_device device %}">{{ device }}</a>
{% endfor %}
</ul>
</div>
{% endblock %}

View File

@ -0,0 +1,41 @@
{% extends "reports/base.html" %}
{% load report_tags %}
{% block css %}
table.profile td {
line-height: inherit;
border-bottom: inherit;
padding: 0 1em 0 0.5em;
}
.treemarker {
color: #666;
font-family: monospace;
white-space: pre;
}
{% endblock %}
{% block content %}
<table>
<tr>
<th>Received
<th>User
<th>Profiler
{% for report in report_page.object_list %}
{% with json=report.data_json %}
<tr {% cycle 'class=alt' '' %}>
<td>{{ report.upload_date|date:"Y-m-d" }}&nbsp;{{ report.upload_date|date:"H:i:s" }}
<td><a href="{% url userreport.views_private.report_user report.user_id_hash %}"><abbr title="{{ report.user_id_hash }}">{{ report.user_id_hash|slice:"0:8" }}</abbr></a>
<td>
<p>Time: {{ json.time }}
<p>Map: {{ json.map }}
{% for name,table in json.profiler.items %}
<h3>{{ name }}</h3>
<table class="profile">
{{ table|format_profile }}
</table>
{% endfor %}
{% endwith %}
{% endfor %}
</table>
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends "reports/base.html" %}
{% load report_tags %}
{% block css %}
td.rawdata {
white-space: pre;
}
{% endblock %}
{% block title %}
User {{ user }}
{% endblock %}
{% block heading %}
All reports from user {{ user }}
{% endblock %}
{% block content %}
<table>
<tr>
<th>Received
<th>Type
<th>Raw data
{% for report in report_page.object_list %}
<tr {% cycle 'class=alt' '' %}>
<td>{{ report.upload_date|date:"Y-m-d" }}&nbsp;{{ report.upload_date|date:"H:i:s" }}
<td>{{ report.data_type }}&nbsp;(v{{ report.data_version }})
<td class=rawdata>{{ report.data|prettify_json }}
{% endfor %}
</table>
{% endblock %}

View File

@ -0,0 +1,99 @@
# http://code.djangoproject.com/attachment/ticket/5908/cycle.py
from django.utils.translation import ungettext, ugettext as _
from django.utils.encoding import force_unicode
from django import template
from django.template import defaultfilters
from django.template import Node, Variable
from django.conf import settings
from itertools import cycle as itertools_cycle
register = template.Library()
class SafeCycleNode(Node):
def __init__(self, cyclevars, variable_name=None):
self.cyclevars = cyclevars
self.cycle_iter = itertools_cycle(cyclevars)
self.variable_name = variable_name
def render(self, context):
if context.has_key('forloop'):
if not context.get(self):
context[self] = True
self.cycle_iter = itertools_cycle(self.cyclevars)
value = self.cycle_iter.next()
value = Variable(value).resolve(context)
if self.variable_name:
context[self.variable_name] = value
return value
#@register.tag
def safe_cycle(parser, token):
"""
Cycles among the given strings each time this tag is encountered.
Within a loop, cycles among the given strings each time through
the loop::
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}
Outside of a loop, give the values a unique name the first time you call
it, then use that name each sucessive time through::
<tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
You can use any number of values, seperated by spaces. Commas can also
be used to separate values; if a comma is used, the cycle values are
interpreted as literal strings.
"""
# Note: This returns the exact same node on each {% cycle name %} call;
# that is, the node object returned from {% cycle a b c as name %} and the
# one returned from {% cycle name %} are the exact same object. This
# shouldn't cause problems (heh), but if it does, now you know.
#
# Ugly hack warning: this stuffs the named template dict into parser so
# that names are only unique within each template (as opposed to using
# a global variable, which would make cycle names have to be unique across
# *all* templates.
args = token.split_contents()
if len(args) < 2:
raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
if ',' in args[1]:
# Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
# case.
args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
if len(args) == 2:
# {% cycle foo %} case.
name = args[1]
if not hasattr(parser, '_namedCycleNodes'):
raise TemplateSyntaxError("No named cycles in template."
" '%s' is not defined" % name)
if not name in parser._namedCycleNodes:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._namedCycleNodes[name]
if len(args) > 4 and args[-2] == 'as':
name = args[-1]
node = SafeCycleNode(args[1:-2], name)
if not hasattr(parser, '_namedCycleNodes'):
parser._namedCycleNodes = {}
parser._namedCycleNodes[name] = node
else:
node = SafeCycleNode(args[1:])
return node
safe_cycle = register.tag(safe_cycle)

View File

@ -0,0 +1,128 @@
# coding=utf-8
from django import template
from django.template.defaultfilters import stringfilter
from django.utils import simplejson
from django.utils.safestring import mark_safe
from django.utils.html import conditional_escape
register = template.Library()
@register.filter
def mod(value, arg):
return value % arg
@register.filter
@stringfilter
def has_token(value, token):
"Returns whether a space-separated list of tokens contains a given token"
return token in value.split(' ')
@register.filter
@stringfilter
def wrap_at_underscores(value):
return value.replace('_', '&#x200b;_')
wrap_at_underscores.is_safe = True
@register.filter
@stringfilter
def prettify_json(value):
try:
data = simplejson.loads(value)
return simplejson.dumps(data, indent=2, sort_keys=True)
except:
return value
@register.filter
@stringfilter
def glext_spec_link(value):
c = value.split('_', 2)
return 'http://www.opengl.org/registry/specs/%s/%s.txt' % (c[1], c[2])
@register.filter
@stringfilter
def prettify_gl_title(value):
if value.startswith('GL_FRAGMENT_PROGRAM_ARB.'):
return value[24:] + ' (fragment)'
if value.startswith('GL_VERTEX_PROGRAM_ARB.'):
return value[22:] + ' (vertex)'
return value
@register.filter
def dictget(value, key):
return value.get(key, '')
@register.filter
def sorteditems(value):
return sorted(value.items(), key = lambda (k, v): k)
@register.filter
def sorteddeviceitems(value):
return sorted(value.items(), key = lambda (k, v): (k['vendor'], k['renderer'], k['os'], v))
@register.filter
def sortedcpuitems(value):
return sorted(value.items(), key = lambda (k, v): (k['x86_vendor'], k['x86_model'], k['x86_family'], k['cpu_identifier']))
@register.filter
def cpufreqformat(value):
return mark_safe("%.2f&nbsp;GHz" % (int(value)/1000000000.0))
@register.filter
def sort(value):
return sorted(value)
@register.filter
def sortreversed(value):
return reversed(sorted(value))
@register.filter
def reverse(value):
return reversed(value)
@register.filter
def format_profile(table):
cols = set()
def extract_cols(t):
for name, row in t.items():
for n in row:
cols.add(n)
if 'children' in row:
extract_cols(row['children'])
extract_cols(table)
if 'children' in cols:
cols.remove('children')
if 'msec/frame' in cols:
cols = ('msec/frame', 'calls/frame', '%/frame', '%/parent', 'mem allocs')
else:
cols = sorted(cols)
out = ['<th>']
for c in cols:
out.append(u'<th>%s' % conditional_escape(c))
def handle(indents, indent, t):
if 'msec/frame' in cols:
items = [d[1] for d in sorted((-float(r.get('msec/frame', '')), (n,r)) for (n,r) in t.items())]
else:
items = sorted(t.items())
item_id = 0
for name, row in items:
if item_id == len(items) - 1:
last = True
else:
last = False
item_id += 1
out.append(u'<tr>')
out.append(u'<td><span class=treemarker>%s%s%s╴</span>%s' % (indent, (u'' if last else u''), (u'' if 'children' in row else u''), conditional_escape(name)))
for c in cols:
out.append(u'<td>%s%s' % ('&nbsp;&nbsp;' * indents, conditional_escape(row.get(c, ''))))
if 'children' in row:
handle(indents+1, indent+(u' ' if last else u''), row['children'])
handle(0, u'', table)
return mark_safe(u'\n'.join(out))

View File

@ -0,0 +1,13 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^upload/v1/$', 'userreport.views.upload'),
(r'^opengl/$', 'userreport.views.report_opengl_index'),
(r'^opengl/json$', 'userreport.views.report_opengl_json'),
(r'^opengl/feature/(?P<feature>[^/]+)$', 'userreport.views.report_opengl_feature'),
(r'^opengl/device/(?P<device>.+)$', 'userreport.views.report_opengl_device'),
(r'^opengl/device', 'userreport.views.report_opengl_device_compare'),
(r'^cpu/$', 'userreport.views.report_cpu'),
)

View File

@ -0,0 +1,8 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^hwdetect/$', 'userreport.views_private.report_hwdetect'),
(r'^messages/$', 'userreport.views_private.report_messages'),
(r'^profile/$', 'userreport.views_private.report_profile'),
(r'^user/([0-9a-f]+)$', 'userreport.views_private.report_user'),
)

View File

@ -0,0 +1,314 @@
from userreport.models import UserReport, UserReport_hwdetect
import userreport.x86 as x86
import hashlib
import datetime
import zlib
from django.http import HttpResponseBadRequest, HttpResponse, Http404, QueryDict
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils import simplejson
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
class hashabledict(dict):
def __hash__(self):
return hash(tuple(sorted(self.items())))
@csrf_exempt
@require_POST
def upload(request):
try:
decompressed = zlib.decompress(request.raw_post_data)
except zlib.error:
return HttpResponseBadRequest('Invalid POST data.\n', content_type = 'text/plain')
POST = QueryDict(decompressed)
try:
user_id = POST['user_id']
generation_date = datetime.datetime.utcfromtimestamp(int(POST['time']))
data_type = POST['type']
data_version = int(POST['version'])
data = POST['data']
except KeyError, e:
return HttpResponseBadRequest('Missing required fields.\n', content_type = 'text/plain')
uploader = request.META['REMOTE_ADDR']
# Fix the IP address if running via proxy on localhost
if uploader == '127.0.0.1':
try:
uploader = request.META['HTTP_X_FORWARDED_FOR'].split(',')[0].strip()
except KeyError:
pass
user_id_hash = hashlib.sha1(user_id).hexdigest()
report = UserReport(
uploader = uploader,
user_id_hash = user_id_hash,
generation_date = generation_date,
data_type = data_type,
data_version = data_version,
data = data
)
report.save()
return HttpResponse('OK', content_type = 'text/plain')
def index(request):
return render_to_response('index.html')
def report_cpu(request):
reports = UserReport_hwdetect.objects
reports = reports.filter(data_type = 'hwdetect', data_version__gte = 4)
all_users = set()
cpus = {}
for report in reports:
json = report.data_json()
if json is None:
continue
cpu = {}
for x in (
'x86_vendor', 'x86_model', 'x86_family',
'cpu_identifier', 'cpu_frequency',
'cpu_numprocs', 'cpu_numpackages', 'cpu_coresperpackage', 'cpu_logicalpercore',
'cpu_numcaches',
'cpu_pagesize', 'cpu_largepagesize',
'numa_numnodes', 'numa_factor', 'numa_interleaved',
):
cpu[x] = json[x]
cpu['os'] = report.os()
def fmt_size(s):
if s % (1024*1024) == 0:
return "%d MB" % (s / (1024*1024))
if s % 1024 == 0:
return "%d kB" % (s / 1024)
return "%d B" % s
def fmt_assoc(w):
if w == 255:
return 'fully-assoc'
else:
return '%d-way' % w
def fmt_cache(c):
types = ('?', 'D', 'I ', 'U')
return "L%d %s: %s (%s, shared %dx, %dB line)" % (
c['level'], types[c['type']], fmt_size(c['totalsize']),
fmt_assoc(c['associativity']), c['sharedby'], c['linesize']
)
def fmt_tlb(c):
types = ('?', 'D', 'I ', 'U')
return "L%d %s: %d-entry (%s, %s page)" % (
c['level'], types[c['type']], c['entries'],
fmt_assoc(c['associativity']), fmt_size(c['pagesize'])
)
def fmt_caches(d, i, cb):
dcaches = d[:]
icaches = i[:]
caches = []
while len(dcaches) or len(icaches):
if len(dcaches) and len(icaches) and dcaches[0] == icaches[0]:
caches.append(cb(dcaches[0]))
dcaches.pop(0)
icaches.pop(0)
else:
if len(dcaches):
caches.append(cb(dcaches[0]))
dcaches.pop(0)
if len(icaches):
caches.append(cb(icaches[0]))
icaches.pop(0)
return tuple(caches)
cpu['caches'] = fmt_caches(json['x86_dcaches'], json['x86_icaches'], fmt_cache)
cpu['tlbs'] = fmt_caches(json['x86_dtlbs'], json['x86_itlbs'], fmt_tlb)
caps = set()
for (n,_,b) in x86.cap_bits:
if n.endswith('[2]'):
continue
if json['x86_caps[%d]' % (b / 32)] & (1 << (b % 32)):
caps.add(n)
cpu['caps'] = frozenset(caps)
all_users.add(report.user_id_hash)
cpus.setdefault(hashabledict(cpu), set()).add(report.user_id_hash)
return render_to_response('reports/cpu.html', {'cpus': cpus, 'x86_cap_descs': x86.cap_descs})
def get_hwdetect_reports():
reports = UserReport_hwdetect.objects
reports = reports.filter(data_type = 'hwdetect', data_version__gte = 3)
return reports
def report_opengl_json(request):
reports = get_hwdetect_reports()
devices = {}
for report in reports:
json = report.data_json()
if json is None:
continue
exts = report.gl_extensions()
limits = report.gl_limits()
devices.setdefault(hashabledict({'vendor': json['GL_VENDOR'], 'renderer': json['GL_RENDERER'], 'os': report.os()}), {}).setdefault((hashabledict(limits), exts), set()).add(json['gfx_drv_ver'])
distinct_devices = []
for (renderer, v) in devices.items():
for (caps, versions) in v.items():
distinct_devices.append((renderer, sorted(versions), caps))
distinct_devices.sort(key = lambda x: (x[0]['vendor'], x[0]['renderer'], x[0]['os'], x))
data = []
for r,vs,(limits,exts) in distinct_devices:
data.append({'device': r, 'versions': vs, 'limits': limits, 'extensions': sorted(exts)})
json = simplejson.dumps(data, indent=1, sort_keys=True)
return HttpResponse(json, content_type = 'text/plain')
def device_identifier(json):
return json['GL_RENDERER']
def report_opengl_index(request):
reports = get_hwdetect_reports()
all_limits = set()
all_exts = set()
all_devices = set()
ext_devices = {}
for report in reports:
json = report.data_json()
if json is None:
continue
device = device_identifier(json)
all_devices.add(device)
exts = report.gl_extensions()
all_exts |= exts
for ext in exts:
ext_devices.setdefault(ext, set()).add(device)
limits = report.gl_limits()
all_limits |= set(limits.keys())
all_limits = sorted(all_limits)
all_exts = sorted(all_exts)
all_devices = sorted(all_devices)
return render_to_response('reports/opengl_index.html', {
'all_limits': all_limits,
'all_exts': all_exts,
'all_devices': all_devices,
'ext_devices': ext_devices,
})
def report_opengl_feature(request, feature):
reports = get_hwdetect_reports()
all_values = set()
values = {}
is_extension = False
for report in reports:
json = report.data_json()
if json is None:
continue
exts = report.gl_extensions()
limits = hashabledict(report.gl_limits())
val = None
if feature in exts:
val = True
is_extension = True
elif feature in limits:
val = limits[feature]
all_values.add(val)
values.setdefault(val, {}).setdefault(hashabledict({'vendor': json['GL_VENDOR'], 'renderer': json['GL_RENDERER'], 'os': report.os()}), set()).add(json['gfx_drv_ver'])
if values.keys() == [None]:
raise Http404
if is_extension:
values = {
'true': values.get(True, {}),
'false': values.get(None, {}),
}
return render_to_response('reports/opengl_feature.html', {
'feature': feature,
'all_values': all_values,
'values': values,
'is_extension': is_extension,
})
def report_opengl_devices(request, selected):
reports = get_hwdetect_reports()
all_limits = set()
all_exts = set()
all_devices = set()
devices = {}
for report in reports:
json = report.data_json()
if json is None:
continue
device = device_identifier(json)
all_devices.add(device)
if device not in selected:
continue
exts = report.gl_extensions()
all_exts |= exts
limits = report.gl_limits()
all_limits |= set(limits.keys())
devices.setdefault(hashabledict({'vendor': json['GL_VENDOR'], 'renderer': json['GL_RENDERER'], 'os': report.os()}), {}).setdefault((hashabledict(limits), exts), set()).add(json['gfx_drv_ver'])
if len(selected) == 1 and len(devices) == 0:
raise Http404
all_limits = sorted(all_limits)
all_exts = sorted(all_exts)
distinct_devices = []
for (renderer, v) in devices.items():
for (caps, versions) in v.items():
distinct_devices.append((renderer, sorted(versions), caps))
distinct_devices.sort(key = lambda x: (x[0]['vendor'], x[0]['renderer'], x[0]['os'], x))
return render_to_response('reports/opengl_device.html', {
'selected': selected,
'all_limits': all_limits,
'all_exts': all_exts,
'all_devices': all_devices,
'devices': distinct_devices,
})
def report_opengl_device(request, device):
return report_opengl_devices(request, [device])
def report_opengl_device_compare(request):
return report_opengl_devices(request, request.GET.getlist('d'))

View File

@ -0,0 +1,45 @@
from userreport.models import UserReport
from django.http import HttpResponseForbidden
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.core.paginator import Paginator, InvalidPage, EmptyPage
def render_reports(request, reports, template, args):
paginator = Paginator(reports, args.get('pagesize', 100))
try:
page = int(request.GET.get('page', '1'))
except ValueError:
page = 1
try:
report_page = paginator.page(page)
except (EmptyPage, InvalidPage):
report_page = paginator.page(paginator.num_pages)
args['report_page'] = report_page
return render_to_response(template, args)
def report_user(request, user):
reports = UserReport.objects.order_by('-upload_date')
reports = reports.filter(user_id_hash = user)
return render_reports(request, reports, 'reports/user.html', {'user': user})
def report_messages(request):
reports = UserReport.objects.order_by('-upload_date')
reports = reports.filter(data_type = 'message', data_version__gte = 1)
return render_reports(request, reports, 'reports/message.html', {})
def report_profile(request):
reports = UserReport.objects.order_by('-upload_date')
reports = reports.filter(data_type = 'profile', data_version__gte = 1)
return render_reports(request, reports, 'reports/profile.html', {'pagesize': 20})
def report_hwdetect(request):
reports = UserReport.objects.order_by('-upload_date')
reports = reports.filter(data_type = 'hwdetect', data_version__gte = 1)
return render_reports(request, reports, 'reports/hwdetect.html', {})

View File

@ -0,0 +1,156 @@
# CPUID feature bits, from LSB to MSB:
# (Names and descriptions gathered from various Intel and AMD sources)
cap_raw = (
# EAX=01H ECX:
"""SSE3
PCLMULQDQ
DTES64: 64-bit debug store
MONITOR: MONITOR/MWAIT
DS-CPL: CPL qualified debug store
VMX: virtual machine extensions
SMX: safer mode extensions
EST: enhanced SpeedStep
TM2: thermal monitor 2
SSSE3
CNXT-ID: L1 context ID
?(ecx11)
FMA: fused multiply add
CMPXCHG16B
xTPR: xTPR update control
PDCM: perfmon and debug capability
?(ecx16)
PCID: process context identifiers
DCA: direct cache access
SSE4_1
SSE4_2
x2APIC: extended xAPIC support
MOVBE
POPCNT
TSC-DEADLINE
AES
XSAVE: XSAVE instructions supported
OSXSAVE: XSAVE instructions enabled
AVX
F16C: half-precision convert
?(ecx30)
RAZ: used by hypervisor to indicate guest status
""" +
# EAX=01H EDX:
"""FPU
VME: virtual 8086 mode enhancements
DE: debugging extension
PSE: page size extension
TSC: time stamp counter
MSR: model specific registers
PAE: physical address extension
MCE: machine-check exception
CMPXCHG8
APIC
?(edx10)
SEP: fast system call
MTRR: memory type range registers
PGE: page global enable
MCA: machine-check architecture
CMOV
PAT: page attribute table
PSE-36: 36-bit page size extension
PSN: processor serial number
CLFSH: CLFLUSH
?(edx20)
DS: debug store
ACPI
MMX
FXSR: FXSAVE and FXSTOR
SSE
SSE2
SS: self-snoop
HTT: hyper-threading
TM: thermal monitor
?(edx30)
PBE: pending break enable
""" +
# EAX=80000001H ECX:
"""LAHF: LAHF/SAHF instructions
CMP: core multi-processing legacy mode
SVM: secure virtual machine
ExtApic
AltMovCr8
ABM: LZCNT instruction
SSE4A
MisAlignSse
3DNowPrefetch
OSVW: OS visible workaround
IBS: instruction based sampling
XOP: extended operation support
SKINIT
WDT: watchdog timer support
?(ext:ecx14)
LWP: lightweight profiling support
FMA4: 4-operand FMA
?(ext:ecx17)
?(ext:ecx18)
NodeId
?(ext:ecx20)
TBM: trailing bit manipulation extensions
TopologyExtensions
?(ext:ecx23)
?(ext:ecx24)
?(ext:ecx25)
?(ext:ecx26)
?(ext:ecx27)
?(ext:ecx28)
?(ext:ecx29)
?(ext:ecx30)
?(ext:ecx31)
""" +
# EAX=80000001H ECX:
"""FPU[2]
VME[2]
DE[2]
PSE[2]
TSC[2]
MSR[2]
PAE[2]
MCE[2]
CMPXCHG8[2]
APIC[2]
?(ext:edx10)
SYSCALL: SYSCALL/SYSRET instructions
MTRR[2]
PGE[2]
MCA[2]
CMOV[2]
PAT[2]
PSE36[2]
?(ext:edx18)
MP: MP-capable
NX: no execute bit
?(ext:edx21)
MmxExt
MMX[2]
FXSR[2]
FFXSR
1GB: 1GB pages
RDTSCP
?(ext:edx28)
x86-64
3DNowExt
3DNow
"""
)
cap_bits = []
cap_descs = {}
idx = 0
for c in cap_raw.strip().split('\n'):
s = c.split(':')
if len(s) == 1:
cap_bits.append((s[0], None, idx))
else:
cap_bits.append((s[0], s[1], idx))
cap_descs[s[0]] = s[1]
idx += 1