Import user-report server side code
This was SVN commit r8986.
This commit is contained in:
parent
4e8c305c6e
commit
f0ea32cb8d
0
source/tools/webservices/__init__.py
Normal file
0
source/tools/webservices/__init__.py
Normal file
11
source/tools/webservices/manage.py
Normal file
11
source/tools/webservices/manage.py
Normal 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)
|
120
source/tools/webservices/settings.py
Normal file
120
source/tools/webservices/settings.py
Normal 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,
|
||||
# },
|
||||
# }
|
||||
#}
|
20
source/tools/webservices/settings_local.EXAMPLE.py
Normal file
20
source/tools/webservices/settings_local.EXAMPLE.py
Normal 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'
|
28
source/tools/webservices/setup.txt
Normal file
28
source/tools/webservices/setup.txt
Normal 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
|
11
source/tools/webservices/urls.py
Normal file
11
source/tools/webservices/urls.py
Normal 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)),
|
||||
)
|
0
source/tools/webservices/userreport/__init__.py
Normal file
0
source/tools/webservices/userreport/__init__.py
Normal file
17
source/tools/webservices/userreport/admin.py
Normal file
17
source/tools/webservices/userreport/admin.py
Normal 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)
|
69
source/tools/webservices/userreport/models.py
Normal file
69
source/tools/webservices/userreport/models.py
Normal 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
|
4
source/tools/webservices/userreport/templates/404.html
Normal file
4
source/tools/webservices/userreport/templates/404.html
Normal file
@ -0,0 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>404</title>
|
||||
File not found.
|
15
source/tools/webservices/userreport/templates/index.html
Normal file
15
source/tools/webservices/userreport/templates/index.html
Normal 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>.
|
@ -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 %}
|
@ -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 × cores per package × 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 }}×{{ cpu.cpu_coresperpackage }}×{{ cpu.cpu_logicalpercore }} = {{ 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 %}
|
@ -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" }} {{ 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 %}
|
@ -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" }} {{ 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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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" }} {{ 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 %}
|
@ -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" }} {{ report.upload_date|date:"H:i:s" }}
|
||||
<td>{{ report.data_type }} (v{{ report.data_version }})
|
||||
<td class=rawdata>{{ report.data|prettify_json }}
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
99
source/tools/webservices/userreport/templatetags/cycle.py
Normal file
99
source/tools/webservices/userreport/templatetags/cycle.py
Normal 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)
|
128
source/tools/webservices/userreport/templatetags/report_tags.py
Normal file
128
source/tools/webservices/userreport/templatetags/report_tags.py
Normal 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('_', '​_')
|
||||
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 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' % (' ' * 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))
|
13
source/tools/webservices/userreport/urls.py
Normal file
13
source/tools/webservices/userreport/urls.py
Normal 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'),
|
||||
)
|
8
source/tools/webservices/userreport/urls_private.py
Normal file
8
source/tools/webservices/userreport/urls_private.py
Normal 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'),
|
||||
)
|
314
source/tools/webservices/userreport/views.py
Normal file
314
source/tools/webservices/userreport/views.py
Normal 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'))
|
45
source/tools/webservices/userreport/views_private.py
Normal file
45
source/tools/webservices/userreport/views_private.py
Normal 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', {})
|
156
source/tools/webservices/userreport/x86.py
Normal file
156
source/tools/webservices/userreport/x86.py
Normal 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
|
Loading…
Reference in New Issue
Block a user