Latest version of user-report server code
This was SVN commit r9393.
This commit is contained in:
parent
0b996bbe75
commit
674eaa1283
8
source/tools/webservices/maint_graphics.py
Normal file
8
source/tools/webservices/maint_graphics.py
Normal file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
|
||||
|
||||
from userreport import maint
|
||||
maint.collect_graphics()
|
83
source/tools/webservices/profilemiddleware.py
Normal file
83
source/tools/webservices/profilemiddleware.py
Normal file
@ -0,0 +1,83 @@
|
||||
# http://www.no-ack.org/2010/12/yet-another-profiling-middleware-for.html
|
||||
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
from cStringIO import StringIO
|
||||
|
||||
from django.conf import settings
|
||||
import hotshot
|
||||
import hotshot.stats
|
||||
|
||||
COMMENT_SYNTAX = ((re.compile(r'^application/(.*\+)?xml|text/html$', re.I), '<!--', '-->'),
|
||||
(re.compile(r'^application/j(avascript|son)$', re.I), '/*', '*/' ))
|
||||
|
||||
class ProfileMiddleware(object):
|
||||
def process_view(self, request, callback, args, kwargs):
|
||||
# Create a profile, writing into a temporary file.
|
||||
filename = tempfile.mktemp()
|
||||
profile = hotshot.Profile(filename)
|
||||
|
||||
try:
|
||||
try:
|
||||
# Profile the call of the view function.
|
||||
response = profile.runcall(callback, request, *args, **kwargs)
|
||||
|
||||
# If we have got a 3xx status code, further
|
||||
# action needs to be taken by the user agent
|
||||
# in order to fulfill the request. So don't
|
||||
# attach any stats to the content, because of
|
||||
# the content is supposed to be empty and is
|
||||
# ignored by the user agent.
|
||||
if response.status_code // 100 == 3:
|
||||
return response
|
||||
|
||||
# Detect the appropriate syntax based on the
|
||||
# Content-Type header.
|
||||
for regex, begin_comment, end_comment in COMMENT_SYNTAX:
|
||||
if regex.match(response['Content-Type'].split(';')[0].strip()):
|
||||
break
|
||||
else:
|
||||
# If the given Content-Type is not
|
||||
# supported, don't attach any stats to
|
||||
# the content and return the unchanged
|
||||
# response.
|
||||
return response
|
||||
|
||||
# The response can hold an iterator, that
|
||||
# is executed when the content property
|
||||
# is accessed. So we also have to profile
|
||||
# the call of the content property.
|
||||
content = profile.runcall(response.__class__.content.fget, response)
|
||||
finally:
|
||||
profile.close()
|
||||
|
||||
# Load the stats from the temporary file and
|
||||
# write them in a human readable format,
|
||||
# respecting some optional settings into a
|
||||
# StringIO object.
|
||||
stats = hotshot.stats.load(filename)
|
||||
if getattr(settings, 'PROFILE_MIDDLEWARE_STRIP_DIRS', False):
|
||||
stats.strip_dirs()
|
||||
if getattr(settings, 'PROFILE_MIDDLEWARE_SORT', None):
|
||||
stats.sort_stats(*settings.PROFILE_MIDDLEWARE_SORT)
|
||||
stats.stream = StringIO()
|
||||
stats.print_stats(*getattr(settings, 'PROFILE_MIDDLEWARE_RESTRICTIONS', []))
|
||||
finally:
|
||||
os.unlink(filename)
|
||||
|
||||
# Construct an HTML/XML or Javascript comment, with
|
||||
# the formatted stats, written to the StringIO object
|
||||
# and attach it to the content of the response.
|
||||
comment = '\n%s\n\n%s\n\n%s\n' % (begin_comment, stats.stream.getvalue().strip(), end_comment)
|
||||
response.content = content + comment
|
||||
|
||||
# If the Content-Length header is given, add the
|
||||
# number of bytes we have added to it. If the
|
||||
# Content-Length header is ommited or incorrect,
|
||||
# it remains so in order to don't change the
|
||||
# behaviour of the web server or user agent.
|
||||
if response.has_header('Content-Length'):
|
||||
response['Content-Length'] = int(response['Content-Length']) + len(comment)
|
||||
|
||||
return response
|
@ -73,8 +73,11 @@ MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
# 'profilemiddleware.ProfileMiddleware',
|
||||
)
|
||||
|
||||
PROFILE_MIDDLEWARE_SORT = ('time', 'calls')
|
||||
|
||||
ROOT_URLCONF = 'urls'
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
@ -101,20 +104,21 @@ INSTALLED_APPS = (
|
||||
# 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,
|
||||
# },
|
||||
# }
|
||||
#}
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
#'include_html': False, # TODO: use once 1.3 final is released
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'django.request':{
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
from userreport.models import UserReport
|
||||
from userreport.models import UserReport, GraphicsDevice, GraphicsExtension, GraphicsLimit
|
||||
from django.contrib import admin
|
||||
|
||||
class UserReportAdmin(admin.ModelAdmin):
|
||||
@ -11,7 +11,20 @@ class UserReportAdmin(admin.ModelAdmin):
|
||||
]
|
||||
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']
|
||||
search_fields = ['=uploader', '=user_id_hash', 'data']
|
||||
date_hierarchy = 'upload_date'
|
||||
|
||||
class GraphicsDeviceAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
class GraphicsExtensionAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
class GraphicsLimitAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
admin.site.register(UserReport, UserReportAdmin)
|
||||
admin.site.register(GraphicsDevice, GraphicsDeviceAdmin)
|
||||
admin.site.register(GraphicsExtension, GraphicsExtensionAdmin)
|
||||
admin.site.register(GraphicsLimit, GraphicsLimitAdmin)
|
||||
|
||||
|
84
source/tools/webservices/userreport/maint.py
Normal file
84
source/tools/webservices/userreport/maint.py
Normal file
@ -0,0 +1,84 @@
|
||||
from userreport.models import UserReport_hwdetect, GraphicsDevice, GraphicsExtension, GraphicsLimit
|
||||
from django.db import connection, transaction
|
||||
|
||||
def collect_graphics():
|
||||
reports = UserReport_hwdetect.objects.filter(data_type = 'hwdetect', data_version__gte = 3)
|
||||
|
||||
print "Gathering data"
|
||||
count = 0
|
||||
|
||||
devices = {}
|
||||
for report in reports:
|
||||
device = report.gl_device_identifier()
|
||||
vendor = report.gl_vendor()
|
||||
renderer = report.gl_renderer()
|
||||
os = report.os()
|
||||
driver = report.gl_driver()
|
||||
exts = report.gl_extensions()
|
||||
limits = report.gl_limits()
|
||||
report.clear_cache()
|
||||
|
||||
devices.setdefault(
|
||||
(device, vendor, renderer, os, driver, exts, tuple(sorted(limits.items()))),
|
||||
set()
|
||||
).add(report.user_id_hash)
|
||||
|
||||
count += 1
|
||||
if count % 100 == 0:
|
||||
print "%d / %d..." % (count, len(reports))
|
||||
|
||||
print "Saving data"
|
||||
count = 0
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
# To get atomic behaviour, construct new tables and then rename them into place at the end
|
||||
try:
|
||||
cursor.execute('DROP TABLE IF EXISTS userreport_graphicsdevice_temp, userreport_graphicsextension_temp, userreport_graphicslimit_temp')
|
||||
except Warning:
|
||||
pass # ignore harmless warnings when tables don't exist
|
||||
cursor.execute('CREATE TABLE userreport_graphicsdevice_temp LIKE userreport_graphicsdevice')
|
||||
cursor.execute('CREATE TABLE userreport_graphicsextension_temp LIKE userreport_graphicsextension')
|
||||
cursor.execute('CREATE TABLE userreport_graphicslimit_temp LIKE userreport_graphicslimit')
|
||||
|
||||
for (device, vendor, renderer, os, driver, exts, limits), users in devices.items():
|
||||
cursor.execute('''
|
||||
INSERT INTO userreport_graphicsdevice_temp (id, device_name, vendor, renderer, os, driver, usercount)
|
||||
VALUES (NULL, %s, %s, %s, %s, %s, %s)
|
||||
''', (device, vendor, renderer, os, driver, len(users)))
|
||||
|
||||
device_id = cursor.lastrowid
|
||||
|
||||
if len(exts):
|
||||
ext_placeholders = ','.join('(NULL, %s, %s)' for ext in exts)
|
||||
ext_values = sum(([device_id, ext] for ext in exts), [])
|
||||
cursor.execute('INSERT INTO userreport_graphicsextension_temp (id, device_id, name) VALUES %s' % ext_placeholders, ext_values)
|
||||
|
||||
if len(limits):
|
||||
limit_placeholders = ','.join('(NULL, %s, %s, %s)' for limit in limits)
|
||||
limit_values = sum(([device_id, name, str(value)] for name,value in limits), [])
|
||||
cursor.execute('INSERT INTO userreport_graphicslimit_temp (id, device_id, name, value) VALUES %s' % limit_placeholders, limit_values)
|
||||
|
||||
count += 1
|
||||
if count % 100 == 0:
|
||||
print "%d / %d..." % (count, len(devices))
|
||||
|
||||
cursor.execute('''
|
||||
RENAME TABLE
|
||||
userreport_graphicsdevice TO userreport_graphicsdevice_old,
|
||||
userreport_graphicsextension TO userreport_graphicsextension_old,
|
||||
userreport_graphicslimit TO userreport_graphicslimit_old,
|
||||
userreport_graphicsdevice_temp TO userreport_graphicsdevice,
|
||||
userreport_graphicsextension_temp TO userreport_graphicsextension,
|
||||
userreport_graphicslimit_temp TO userreport_graphicslimit
|
||||
''')
|
||||
cursor.execute('''
|
||||
DROP TABLE IF EXISTS
|
||||
userreport_graphicsdevice_old,
|
||||
userreport_graphicsextension_old,
|
||||
userreport_graphicslimit_old
|
||||
''')
|
||||
|
||||
transaction.commit_unless_managed()
|
||||
|
||||
print "Finished"
|
@ -33,6 +33,15 @@ class UserReport(models.Model):
|
||||
self.cached_json = None
|
||||
return self.cached_json
|
||||
|
||||
def data_json_nocache(self):
|
||||
try:
|
||||
return simplejson.loads(self.data)
|
||||
except:
|
||||
return None
|
||||
|
||||
def clear_cache(self):
|
||||
delattr(self, 'cached_json')
|
||||
|
||||
def downcast(self):
|
||||
if self.data_type == 'hwdetect':
|
||||
return UserReport_hwdetect.objects.get(id = self.id)
|
||||
@ -62,15 +71,16 @@ class UserReport_hwdetect(UserReport):
|
||||
return None
|
||||
# The renderer string should typically be interpreted as UTF-8
|
||||
try:
|
||||
return json['GL_RENDERER'].encode('iso-8859-1').decode('utf-8')
|
||||
return json['GL_RENDERER'].encode('iso-8859-1').decode('utf-8').strip()
|
||||
except UnicodeError:
|
||||
return json['GL_RENDERER']
|
||||
return json['GL_RENDERER'].strip()
|
||||
|
||||
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(' '))
|
||||
vals = re.split(r'\s+', json['GL_EXTENSIONS'])
|
||||
return frozenset(v for v in vals if len(v)) # skip empty strings (e.g. no extensions at all, or leading/trailing space)
|
||||
|
||||
def gl_limits(self):
|
||||
json = self.data_json()
|
||||
@ -91,6 +101,10 @@ class UserReport_hwdetect(UserReport):
|
||||
# Hide some values that got deleted from the report in r8953, for consistency
|
||||
if k in ('GL_MAX_COLOR_MATRIX_STACK_DEPTH', 'GL_FRAGMENT_PROGRAM_ARB.GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB', 'GL_FRAGMENT_PROGRAM_ARB.GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB'):
|
||||
continue
|
||||
# Hide some pixel depth values that are not really correlated with device
|
||||
if k in ('GL_RED_BITS', 'GL_GREEN_BITS', 'GL_BLUE_BITS', 'GL_ALPHA_BITS', 'GL_INDEX_BITS', 'GL_DEPTH_BITS', 'GL_STENCIL_BITS',
|
||||
'GL_ACCUM_RED_BITS', 'GL_ACCUM_GREEN_BITS', 'GL_ACCUM_BLUE_BITS', 'GL_ACCUM_ALPHA_BITS'):
|
||||
continue
|
||||
limits[k] = v
|
||||
return limits
|
||||
|
||||
@ -98,17 +112,26 @@ class UserReport_hwdetect(UserReport):
|
||||
# (skipping boring hardware/driver details)
|
||||
def gl_device_identifier(self):
|
||||
r = self.gl_renderer()
|
||||
m = re.match(r'^(?:ATI |Mesa DRI )?(.*?)\s*(?:GEM 20100330 DEVELOPMENT|GEM 20091221 2009Q4|20090101|Series)?\s*(?:x86|/AGP|/PCI|/MMX|/MMX\+|/SSE|/SSE2|/3DNOW!|/3DNow!\+)*(?: TCL| NO-TCL)?(?: DRI2)?$', r)
|
||||
m = re.match(r'^(?:AMD |ATI |NVIDIA |Mesa DRI )?(.*?)\s*(?:GEM 20100328 2010Q1|GEM 20100330 DEVELOPMENT|GEM 20091221 2009Q4|20090101|Series)?\s*(?:x86|/AGP|/PCI|/MMX|/MMX\+|/SSE|/SSE2|/3DNOW!|/3DNow!|/3DNow!\+)*(?: TCL| NO-TCL)?(?: DRI2)?(?: \(Microsoft Corporation - WDDM\))?(?: OpenGL Engine)?\s*$', r)
|
||||
if m:
|
||||
r = m.group(1)
|
||||
return r
|
||||
return r.strip()
|
||||
|
||||
def gl_vendor(self):
|
||||
json = self.data_json()
|
||||
return json['GL_VENDOR'].strip()
|
||||
|
||||
# Construct a nice string identifying the driver
|
||||
def gl_driver(self):
|
||||
json = self.data_json()
|
||||
gfx_drv_ver = json['gfx_drv_ver']
|
||||
|
||||
# Try the Mesa style first
|
||||
# Try the Mesa git style first
|
||||
m = re.match(r'^OpenGL \d+\.\d+(?:\.\d+)? (Mesa \d+\.\d+)-devel \(git-([a-f0-9]+)', gfx_drv_ver)
|
||||
if m:
|
||||
return '%s-git-%s' % (m.group(1), m.group(2))
|
||||
|
||||
# Try the normal Mesa style
|
||||
m = re.match(r'^OpenGL \d+\.\d+(?:\.\d+)? (Mesa .*)$', gfx_drv_ver)
|
||||
if m:
|
||||
return m.group(1)
|
||||
@ -119,10 +142,15 @@ class UserReport_hwdetect(UserReport):
|
||||
return m.group(1)
|
||||
|
||||
# Try the ATI Catalyst Linux style
|
||||
m = re.match(r'^OpenGL (\d+\.\d+\.\d+) Compatibility Profile Context$', gfx_drv_ver)
|
||||
m = re.match(r'^OpenGL (\d+\.\d+\.\d+) Compatibility Profile Context(?: FireGL)?$', gfx_drv_ver)
|
||||
if m:
|
||||
return m.group(1)
|
||||
|
||||
# Try the non-direct-rendering ATI Catalyst Linux style
|
||||
m = re.match(r'^OpenGL 1\.4 \((\d+\.\d+\.\d+) Compatibility Profile Context(?: FireGL)?\)$', gfx_drv_ver)
|
||||
if m:
|
||||
return '%s (indirect)' % m.group(1)
|
||||
|
||||
# Try to guess the relevant Windows driver
|
||||
# (These are the ones listed in lib/sysdep/os/win/wgfx.cpp)
|
||||
|
||||
@ -138,6 +166,10 @@ class UserReport_hwdetect(UserReport):
|
||||
if json['GL_VENDOR'] in ('ATI Technologies Inc.', 'Advanced Micro Devices, Inc.'):
|
||||
m = re.search(r'atioglxx.dll \((.*?)\)', gfx_drv_ver)
|
||||
if m: return m.group(1)
|
||||
m = re.search(r'atioglx2.dll \((.*?)\)', gfx_drv_ver)
|
||||
if m: return m.group(1)
|
||||
m = re.search(r'atioglaa.dll \((.*?)\)', gfx_drv_ver)
|
||||
if m: return m.group(1)
|
||||
|
||||
if json['GL_VENDOR'] == 'Intel':
|
||||
# Assume 64-bit takes precedence
|
||||
@ -148,5 +180,28 @@ class UserReport_hwdetect(UserReport):
|
||||
# Legacy 32-bit
|
||||
m = re.search(r'iglicd32.dll \((.*?)\)', gfx_drv_ver)
|
||||
if m: return m.group(1)
|
||||
m = re.search(r'ialmgicd32.dll \((.*?)\)', gfx_drv_ver)
|
||||
if m: return m.group(1)
|
||||
m = re.search(r'ialmgicd.dll \((.*?)\)', gfx_drv_ver)
|
||||
if m: return m.group(1)
|
||||
|
||||
return gfx_drv_ver
|
||||
|
||||
|
||||
class GraphicsDevice(models.Model):
|
||||
device_name = models.CharField(max_length = 128, db_index = True)
|
||||
vendor = models.CharField(max_length = 64)
|
||||
renderer = models.CharField(max_length = 128)
|
||||
os = models.CharField(max_length = 16)
|
||||
driver = models.CharField(max_length = 128)
|
||||
usercount = models.IntegerField()
|
||||
|
||||
class GraphicsExtension(models.Model):
|
||||
device = models.ForeignKey(GraphicsDevice)
|
||||
name = models.CharField(max_length = 128, db_index = True)
|
||||
|
||||
class GraphicsLimit(models.Model):
|
||||
device = models.ForeignKey(GraphicsDevice)
|
||||
name = models.CharField(max_length = 128, db_index = True)
|
||||
value = models.CharField(max_length = 64)
|
||||
|
||||
|
@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>0 A.D. report service</title>
|
||||
<style>
|
||||
body {
|
||||
font-size: 12px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
b {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
<p>(<a href="{% url userreport.views.report_opengl_index %}">Back to index page.</a>)</p>
|
||||
|
||||
<p>The <a href="{% url userreport.views.report_opengl_json %}">JSON data</a> currently
|
||||
has the format shown below.</p>
|
||||
|
||||
<p>Each entry in the outer array is a single distinct set of device features
|
||||
(array of <code>"extensions"</code> plus hash of implementation-dependent <code>"limits"</code>).</p>
|
||||
|
||||
<p>Each feature set is associated with an array of <code>"devices"</code>.
|
||||
For each of those devices, we have received a report containing that particular feature set;
|
||||
this is effectively just a compression mechanism so we don't have to duplicate the entire
|
||||
feature set description when dozens of devices have identical features.</p>
|
||||
|
||||
<p>Each device has an <code>"os"</code> (<code>"Windows"</code>, <code>"Linux"</code>, <code>"OS X"</code>),
|
||||
a <code>"renderer"</code> (from <code>GL_RENDERER</code>),
|
||||
a <code>"vendor"</code> (from <code>GL_VENDOR</code>),
|
||||
and a <code>"driver"</code> (typically derived from the appropriate DLL on Windows,
|
||||
or sometimes a list of lots of DLLs if we can't figure out which is appropriate,
|
||||
or derived from the full <code>GL_VERSION</code> string on Linux).</p>
|
||||
|
||||
<pre>
|
||||
[
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"driver": "6.14.10.8494",
|
||||
"os": "Windows",
|
||||
"renderer": "AMD 760G",
|
||||
"vendor": "ATI Technologies Inc."
|
||||
}
|
||||
],
|
||||
"extensions": [
|
||||
"GL_AMD_performance_monitor",
|
||||
<b>...</b>
|
||||
"WGL_EXT_swap_control"
|
||||
],
|
||||
"limits": {
|
||||
"GL_ALIASED_LINE_WIDTH_RANGE[0]": "1",
|
||||
<b>...</b>
|
||||
"GL_VERTEX_PROGRAM_ARB.GL_MAX_PROGRAM_TEMPORARIES_ARB": "160"
|
||||
}
|
||||
},
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"driver": "6.14.10.10057",
|
||||
"os": "Windows",
|
||||
"renderer": "AMD M880G with ATI Mobility Radeon HD 4200",
|
||||
"vendor": "ATI Technologies Inc."
|
||||
},
|
||||
{
|
||||
"driver": "6.14.10.10179",
|
||||
"os": "Windows",
|
||||
"renderer": "AMD M880G with ATI Mobility Radeon HD 4250",
|
||||
"vendor": "ATI Technologies Inc."
|
||||
},
|
||||
{
|
||||
"driver": "3.3.10188",
|
||||
"os": "Linux",
|
||||
"renderer": "ATI Mobility Radeon HD 3400 Series",
|
||||
"vendor": "ATI Technologies Inc."
|
||||
},
|
||||
{
|
||||
"driver": "6.14.10.10151",
|
||||
"os": "Windows",
|
||||
"renderer": "ATI Mobility Radeon HD 3400 Series",
|
||||
"vendor": "ATI Technologies Inc."
|
||||
},
|
||||
<b>...</b>
|
||||
],
|
||||
"extensions": [
|
||||
"GL_AMDX_debug_output",
|
||||
<b>...</b>
|
||||
"WGL_EXT_swap_control"
|
||||
],
|
||||
"limits": {
|
||||
"GL_ALIASED_LINE_WIDTH_RANGE[0]": "1",
|
||||
<b>...</b>
|
||||
"GL_VERTEX_PROGRAM_ARB.GL_MAX_PROGRAM_TEMPORARIES_ARB": "160"
|
||||
}
|
||||
},
|
||||
<b>...</b>
|
||||
}
|
||||
</pre>
|
||||
|
@ -8,11 +8,19 @@ td.true { background: #3f3; }
|
||||
td.false { background: #f33; }
|
||||
td.true.alt { background: #2e2; }
|
||||
td.false.alt { background: #e22; }
|
||||
td.changed { font-weight: bold; }
|
||||
|
||||
.device-status ul {
|
||||
.devices {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.devices ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-left: 2em;
|
||||
list-style-type: none;
|
||||
font-weight: normal;
|
||||
font-size: smaller;
|
||||
white-space: pre;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
@ -28,26 +36,30 @@ OpenGL capabilities report
|
||||
|
||||
<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>
|
||||
<p>The table here shows the features reported for devices with the following GL_RENDERER strings:</p>
|
||||
<ul>
|
||||
{% for device in selected %}<li>{{ device }}{% endfor %}
|
||||
{% for r in gl_renderers|sort %}<li>{{ r }}</li>
|
||||
{% 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>
|
||||
There is a column for each distinct set of reported features.
|
||||
The column heading gives the short device name, and the set of
|
||||
driver versions with that feature set.</p>
|
||||
|
||||
<p>Green cells indicate supported extensions; red cells indicate non-supported extensions.
|
||||
Cells are marked with bold when their value differs from the previous cell in the same row.</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' '' %}>
|
||||
<th class="devices{% safe_cycle ' alt' '' %}">
|
||||
{{ device.0.device }} ({{ device.0.os }}):
|
||||
<ul>{% for driver in device.1 %}<li>{{ driver }}{% endfor %}</ul>
|
||||
{% endfor %}
|
||||
@ -64,7 +76,7 @@ There is a column for each distinct set of reported features.</p>
|
||||
{% if not forloop.counter|mod:30 %}
|
||||
<tr><td>
|
||||
{% for device in devices %}
|
||||
<th{% safe_cycle ' class=alt' '' %}>
|
||||
<th class="devices{% safe_cycle ' alt' '' %}">
|
||||
{{ device.0.device }} ({{ device.0.os }}):
|
||||
<ul>{% for driver in device.1 %}<li>{{ driver }}{% endfor %}</ul>
|
||||
{% endfor %}
|
||||
@ -73,7 +85,7 @@ There is a column for each distinct set of reported features.</p>
|
||||
<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 }}
|
||||
<td class="{% safe_cycle 'alt' '' %}{% ifchanged device.2.0|dictget:limit %}{% if not forloop.first %} changed{% endif %}{% endifchanged %}">{{ device.2.0|dictget:limit }}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
@ -24,6 +24,22 @@ tr.head {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
tr.true {
|
||||
background: #f6fff6;
|
||||
}
|
||||
|
||||
tr.true.alt {
|
||||
background: #f0fff0;
|
||||
}
|
||||
|
||||
tr.false {
|
||||
background: #fff6f6;
|
||||
}
|
||||
|
||||
tr.false.alt {
|
||||
background: #fff0f0;
|
||||
}
|
||||
|
||||
tr.data:hover {
|
||||
background: #ddd;
|
||||
}
|
||||
@ -47,58 +63,81 @@ OpenGL capabilities report: {{ feature }}
|
||||
|
||||
<table class=support-status>
|
||||
|
||||
{% if values.true %}
|
||||
<tr>
|
||||
<td colspan=4><h2>Supported by:</h2>
|
||||
<td colspan=4><h2>Supported by
|
||||
{{ usercounts.true }} user{{ usercounts.true|pluralize }}:</h2>
|
||||
|
||||
<tr class=head>
|
||||
<th>Vendor
|
||||
<th>Renderer
|
||||
<th>Users
|
||||
<th>OS
|
||||
<th>Driver versions
|
||||
|
||||
{% for device in values.true|sorteddeviceitems %}
|
||||
<tr class="data{% safe_cycle '' ' alt'%}">
|
||||
<tr class="data true{% safe_cycle '' ' alt'%}">
|
||||
<td>{{ device.0.vendor }}
|
||||
<td class=r><a href="{% url userreport.views.report_opengl_device device.0.device %}">{{ device.0.renderer }}</a>
|
||||
<td>{{ device.1.usercount }}
|
||||
<td>{{ device.0.os }}
|
||||
<td><ul>
|
||||
{% for driver in device.1|sort %}<li>{{ driver }}{% endfor %}
|
||||
{% for driver in device.1.drivers|sort %}<li>{{ driver }}{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if values.false %}
|
||||
<tr>
|
||||
<td colspan=4><h2>Not supported by:</h2>
|
||||
<td colspan=4><h2>Not supported by
|
||||
{{ usercounts.false }} user{{ usercounts.false|pluralize }}:</h2>
|
||||
|
||||
<tr class=head>
|
||||
<th>Vendor
|
||||
<th>Renderer
|
||||
<th>Users
|
||||
<th>OS
|
||||
<th>Driver versions
|
||||
|
||||
{% for device in values.false|sorteddeviceitems %}
|
||||
<tr class="data{% safe_cycle '' ' alt'%}">
|
||||
<tr class="data false{% safe_cycle '' ' alt'%}">
|
||||
<td>{{ device.0.vendor }}
|
||||
<td class=r><a href="{% url userreport.views.report_opengl_device device.0.device %}">{{ device.0.renderer }}</a>
|
||||
<td>{{ device.1.usercount }}
|
||||
<td>{{ device.0.os }}
|
||||
<td><ul>
|
||||
{% for driver in device.1|sort %}<li>{{ driver }}{% endfor %}
|
||||
{% for driver in device.1.drivers|sort %}<li>{{ driver }}{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
</table>
|
||||
|
||||
{% else %}
|
||||
|
||||
<table class=support-status>
|
||||
<tr>
|
||||
<th>Value
|
||||
<th>Number of users
|
||||
{% for val in values.keys|sortreversed %}
|
||||
<tr>
|
||||
<td><a href="#{{ val|urlencode }}">{{ val|default_if_none:"Unsupported/unknown" }}</a>
|
||||
<td>{{ usercounts|dictget:val }} ({% widthratio usercounts|dictget:val num_users 100 %}%)
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<table class=support-status>
|
||||
|
||||
{% for val in values.keys|sortreversed %}
|
||||
|
||||
<tr>
|
||||
<td colspan=4><h2>Value: {{ val|default_if_none:"Unsupported/unknown" }}</h2>
|
||||
<td colspan=4 id="{{ val }}"><h2>Value "{{ val|default_if_none:"Unsupported/unknown" }}"
|
||||
({{ usercounts|dictget:val }} user{{ usercounts|dictget:val|pluralize }}):</h2>
|
||||
|
||||
<tr class=head>
|
||||
<th>Vendor
|
||||
<th>Renderer
|
||||
<th>Users
|
||||
<th>OS
|
||||
<th>Driver versions
|
||||
|
||||
@ -106,9 +145,10 @@ OpenGL capabilities report: {{ feature }}
|
||||
<tr class="data{% safe_cycle '' ' alt'%}">
|
||||
<td>{{ device.0.vendor }}
|
||||
<td class=r><a href="{% url userreport.views.report_opengl_device device.0.device %}">{{ device.0.renderer }}</a>
|
||||
<td>{{ device.1.usercount }}
|
||||
<td>{{ device.0.os }}
|
||||
<td><ul>
|
||||
{% for driver in device.1|sort %}<li>{{ driver }}{% endfor %}
|
||||
{% for driver in device.1.drivers|sort %}<li>{{ driver }}{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
|
||||
|
@ -27,6 +27,12 @@ li:hover {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin: 2px 10px 2px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
@ -64,15 +70,28 @@ function sort_by_name(ul)
|
||||
</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>.
|
||||
See the <a href="{% url userreport.views.index %}">index page</a> for more stuff.
|
||||
Contact <a href="mailto:excors@gmail.com">Philip Taylor</a> for questions.</p>
|
||||
|
||||
<p>Browse the data here, or download as <a href="{% url userreport.views.report_opengl_json %}">JSON</a>
|
||||
(see <a href="{% url userreport.views.report_opengl_json_format %}">format description</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>
|
||||
|
||||
<p>The listed extensions are based on the GL_EXTENSIONS string:
|
||||
we don't show an extension as supported if it's not explicitly advertised,
|
||||
even if GL_VERSION is a version where that extension was folded into
|
||||
the main spec.</p>
|
||||
|
||||
<p>The extension support percentages are based on the number of user/device/driver combinations,
|
||||
from a total of {{ num_users }}. This is obviously hopelessly biased and unrepresentative
|
||||
so don't read too much into the numbers.</p>
|
||||
|
||||
<p>The listed device names are based on GL_RENDERER, with boring components stripped out.
|
||||
Driver versions on Windows are determined from DLL versions; if we can't guess which is the
|
||||
correct DLL then we try to list all the detectable DLLs. Driver versions on Linux are determined
|
||||
from GL_VERSION, when it's encoded in there.
|
||||
</p>
|
||||
|
||||
<div class=col>
|
||||
<h2>Extension support</h2>
|
||||
Sort by
|
||||
@ -80,18 +99,20 @@ Sort by
|
||||
<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>)
|
||||
<li data-supportedby={{ ext_devices|dictget:ext }} data-extname="{{ ext }}">
|
||||
<span class=progress title="Percentage of users with this extension"><span class=bar style="width:{% widthratio ext_devices|dictget:ext num_users 40 %}px">{% widthratio ext_devices|dictget:ext num_users 100 %}%</span></span>
|
||||
(<a href="{{ ext|glext_spec_link }}" title="Link to specification">s</a>)
|
||||
<a href="{% url userreport.views.report_opengl_feature ext %}">{{ ext }}</a>
|
||||
{% if ext in ext_versions %}(~GL{{ ext_versions|dictget:ext }}){% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class=col>
|
||||
<h2>Implementation limits</h2>
|
||||
<ul>
|
||||
{% for limit in all_limits %}
|
||||
<li><a href="{% url userreport.views.report_opengl_feature limit %}">{{ limit }}</a>
|
||||
<li><a href="{% url userreport.views.report_opengl_feature limit %}">{{ limit|prettify_gl_title }}</a>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
@ -100,7 +121,7 @@ Sort by
|
||||
<h2>Device details</h2>
|
||||
<ul>
|
||||
{% for device,users in all_devices|sorteditems %}
|
||||
<li><a href="{% url userreport.views.report_opengl_device device %}">{{ device }}</a> {% if 0 %}({{ users|length }} users){% endif %}
|
||||
<li><a href="{% url userreport.views.report_opengl_device device %}">{{ device }}</a> {% if 0 %}({{ users }} users){% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -37,15 +37,18 @@ def prettify_json(value):
|
||||
@stringfilter
|
||||
def glext_spec_link(value):
|
||||
c = value.split('_', 2)
|
||||
if len(c) < 2: return ''
|
||||
return 'http://www.opengl.org/registry/specs/%s/%s.txt' % (c[1], c[2])
|
||||
|
||||
@register.filter
|
||||
@stringfilter
|
||||
def prettify_gl_title(value):
|
||||
if value[-4:] in ('_ARB', '_EXT'):
|
||||
value = value[:-4]
|
||||
if value.startswith('GL_FRAGMENT_PROGRAM_ARB.'):
|
||||
return value[24:] + ' (fragment)'
|
||||
value = value[24:] + ' (fragment)'
|
||||
if value.startswith('GL_VERTEX_PROGRAM_ARB.'):
|
||||
return value[22:] + ' (vertex)'
|
||||
value = value[22:] + ' (vertex)'
|
||||
return value
|
||||
|
||||
@register.filter
|
||||
|
@ -5,9 +5,12 @@ urlpatterns = patterns('',
|
||||
|
||||
(r'^opengl/$', 'userreport.views.report_opengl_index'),
|
||||
(r'^opengl/json$', 'userreport.views.report_opengl_json'),
|
||||
(r'^opengl/json/format$', 'userreport.views.report_opengl_json_format'),
|
||||
(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'),
|
||||
|
||||
(r'^usercount/$', 'userreport.views.report_usercount'),
|
||||
)
|
||||
|
@ -2,9 +2,12 @@ from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^hwdetect/$', 'userreport.views_private.report_hwdetect'),
|
||||
(r'^hwdetect_test_data/$', 'userreport.views_private.report_hwdetect_test_data'),
|
||||
(r'^messages/$', 'userreport.views_private.report_messages'),
|
||||
(r'^profile/$', 'userreport.views_private.report_profile'),
|
||||
(r'^performance/$', 'userreport.views_private.report_performance'),
|
||||
(r'^gfx/$', 'userreport.views_private.report_gfx'),
|
||||
(r'^ram/$', 'userreport.views_private.report_ram'),
|
||||
(r'^os/$', 'userreport.views_private.report_os'),
|
||||
(r'^user/([0-9a-f]+)$', 'userreport.views_private.report_user'),
|
||||
)
|
||||
|
@ -1,15 +1,17 @@
|
||||
from userreport.models import UserReport, UserReport_hwdetect
|
||||
from userreport.models import UserReport, UserReport_hwdetect, GraphicsDevice, GraphicsExtension, GraphicsLimit
|
||||
import userreport.x86 as x86
|
||||
import userreport.gl
|
||||
|
||||
import hashlib
|
||||
import datetime
|
||||
import zlib
|
||||
import re
|
||||
|
||||
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.db import connection, transaction
|
||||
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
@ -67,13 +69,14 @@ def index(request):
|
||||
|
||||
def report_cpu(request):
|
||||
reports = UserReport_hwdetect.objects
|
||||
reports = reports.filter(data_type = 'hwdetect', data_version__gte = 4)
|
||||
reports = reports.filter(data_type = 'hwdetect', data_version__gte = 4, data_version__lte = 5)
|
||||
# TODO: add v6 support
|
||||
|
||||
all_users = set()
|
||||
cpus = {}
|
||||
|
||||
for report in reports:
|
||||
json = report.data_json()
|
||||
json = report.data_json_nocache()
|
||||
if json is None:
|
||||
continue
|
||||
|
||||
@ -140,8 +143,11 @@ def report_cpu(request):
|
||||
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)
|
||||
try:
|
||||
cpu['caches'] = fmt_caches(json['x86_dcaches'], json['x86_icaches'], fmt_cache)
|
||||
cpu['tlbs'] = fmt_caches(json['x86_dtlbs'], json['x86_itlbs'], fmt_tlb)
|
||||
except TypeError:
|
||||
continue # skip on bogus cache data
|
||||
|
||||
caps = set()
|
||||
for (n,_,b) in x86.cap_bits:
|
||||
@ -156,61 +162,67 @@ def report_cpu(request):
|
||||
|
||||
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 = {}
|
||||
|
||||
reports = GraphicsDevice.objects.all()
|
||||
for report in reports:
|
||||
json = report.data_json()
|
||||
if json is None:
|
||||
continue
|
||||
exts = frozenset(e.name for e in report.graphicsextension_set.all())
|
||||
limits = dict((l.name, l.value) for l in report.graphicslimit_set.all())
|
||||
|
||||
exts = report.gl_extensions()
|
||||
limits = report.gl_limits()
|
||||
device = (report.vendor, report.renderer, report.os, report.driver)
|
||||
devices.setdefault((hashabledict(limits), exts), set()).add(device)
|
||||
|
||||
devices.setdefault(hashabledict({'renderer': report.gl_renderer(), 'os': report.os()}), {}).setdefault((hashabledict(limits), exts), set()).add(report.gl_driver())
|
||||
|
||||
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]['renderer'], x[0]['os'], x))
|
||||
sorted_devices = sorted(devices.items(), key = lambda (k,deviceset): sorted(deviceset))
|
||||
|
||||
data = []
|
||||
for r,vs,(limits,exts) in distinct_devices:
|
||||
data.append({'device': r, 'drivers': vs, 'limits': limits, 'extensions': sorted(exts)})
|
||||
for (limits,exts),deviceset in sorted_devices:
|
||||
devices = [
|
||||
{'vendor': v, 'renderer': r, 'os': o, 'driver': d}
|
||||
for (v,r,o,d) in sorted(deviceset)
|
||||
]
|
||||
data.append({'devices': devices, 'limits': limits, 'extensions': sorted(exts)})
|
||||
json = simplejson.dumps(data, indent=1, sort_keys=True)
|
||||
return HttpResponse(json, content_type = 'text/plain')
|
||||
|
||||
def report_opengl_json_format(request):
|
||||
return render_to_response('jsonformat.html')
|
||||
|
||||
def report_opengl_index(request):
|
||||
reports = get_hwdetect_reports()
|
||||
cursor = connection.cursor()
|
||||
|
||||
all_limits = set()
|
||||
all_exts = set()
|
||||
all_devices = {}
|
||||
ext_devices = {}
|
||||
cursor.execute('''
|
||||
SELECT SUM(usercount)
|
||||
FROM userreport_graphicsdevice
|
||||
''')
|
||||
num_users = sum(c for (c,) in cursor)
|
||||
|
||||
for report in reports:
|
||||
json = report.data_json()
|
||||
if json is None:
|
||||
continue
|
||||
cursor.execute('''
|
||||
SELECT name, SUM(usercount)
|
||||
FROM userreport_graphicsextension e
|
||||
JOIN userreport_graphicsdevice d
|
||||
ON e.device_id = d.id
|
||||
GROUP BY name
|
||||
''')
|
||||
exts = cursor.fetchall()
|
||||
all_exts = set(n for n,c in exts)
|
||||
ext_devices = dict((n,c) for n,c in exts)
|
||||
|
||||
device = report.gl_device_identifier()
|
||||
all_devices.setdefault(device, set()).add(report.user_id_hash)
|
||||
cursor.execute('''
|
||||
SELECT name
|
||||
FROM userreport_graphicslimit l
|
||||
JOIN userreport_graphicsdevice d
|
||||
ON l.device_id = d.id
|
||||
GROUP BY name
|
||||
''')
|
||||
all_limits = set(n for (n,) in cursor.fetchall())
|
||||
|
||||
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())
|
||||
cursor.execute('''
|
||||
SELECT device_name, SUM(usercount)
|
||||
FROM userreport_graphicsdevice
|
||||
GROUP BY device_name
|
||||
''')
|
||||
all_devices = dict((n,c) for n,c in cursor.fetchall())
|
||||
|
||||
all_limits = sorted(all_limits)
|
||||
all_exts = sorted(all_exts)
|
||||
@ -220,76 +232,107 @@ def report_opengl_index(request):
|
||||
'all_exts': all_exts,
|
||||
'all_devices': all_devices,
|
||||
'ext_devices': ext_devices,
|
||||
'num_users': num_users,
|
||||
'ext_versions': userreport.gl.glext_versions,
|
||||
})
|
||||
|
||||
def report_opengl_feature(request, feature):
|
||||
reports = get_hwdetect_reports()
|
||||
|
||||
all_values = set()
|
||||
usercounts = {}
|
||||
values = {}
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
is_extension = False
|
||||
|
||||
for report in reports:
|
||||
json = report.data_json()
|
||||
if json is None:
|
||||
continue
|
||||
|
||||
device = report.gl_device_identifier()
|
||||
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': report.gl_renderer(), 'os': report.os(), 'device': device}), set()).add(report.gl_driver())
|
||||
|
||||
if values.keys() == [None]:
|
||||
raise Http404
|
||||
if re.search(r'[a-z]', feature):
|
||||
is_extension = True
|
||||
|
||||
if is_extension:
|
||||
values = {
|
||||
'true': values.get(True, {}),
|
||||
'false': values.get(None, {}),
|
||||
}
|
||||
cursor.execute('''
|
||||
SELECT vendor, renderer, os, driver, device_name, SUM(usercount),
|
||||
(SELECT 1 FROM userreport_graphicsextension e WHERE e.name = %s AND e.device_id = d.id) AS val
|
||||
FROM userreport_graphicsdevice d
|
||||
GROUP BY vendor, renderer, os, driver, device_name, val
|
||||
''', [feature])
|
||||
|
||||
for vendor, renderer, os, driver, device_name, usercount, val in cursor:
|
||||
val = 'true' if val else 'false'
|
||||
all_values.add(val)
|
||||
usercounts[val] = usercounts.get(val, 0) + usercount
|
||||
v = values.setdefault(val, {}).setdefault(hashabledict({
|
||||
'vendor': vendor,
|
||||
'renderer': renderer,
|
||||
'os': os,
|
||||
'device': device_name
|
||||
}), {'usercount': 0, 'drivers': set()})
|
||||
v['usercount'] += usercount
|
||||
v['drivers'].add(driver)
|
||||
|
||||
else:
|
||||
cursor.execute('''
|
||||
SELECT value, vendor, renderer, os, driver, device_name, usercount
|
||||
FROM userreport_graphicslimit l
|
||||
JOIN userreport_graphicsdevice d
|
||||
ON l.device_id = d.id
|
||||
WHERE name = %s
|
||||
''', [feature])
|
||||
|
||||
for val, vendor, renderer, os, driver, device_name, usercount in cursor:
|
||||
# Convert to int/float if possible, for better sorting
|
||||
try: val = int(val)
|
||||
except ValueError:
|
||||
try: val = float(val)
|
||||
except ValueError: pass
|
||||
|
||||
all_values.add(val)
|
||||
usercounts[val] = usercounts.get(val, 0) + usercount
|
||||
v = values.setdefault(val, {}).setdefault(hashabledict({
|
||||
'vendor': vendor,
|
||||
'renderer': renderer,
|
||||
'os': os,
|
||||
'device': device_name
|
||||
}), {'usercount': 0, 'drivers': set()})
|
||||
v['usercount'] += usercount
|
||||
v['drivers'].add(driver)
|
||||
|
||||
if values.keys() == [] or values.keys() == ['false']:
|
||||
raise Http404
|
||||
|
||||
num_users = sum(usercounts.values())
|
||||
|
||||
return render_to_response('reports/opengl_feature.html', {
|
||||
'feature': feature,
|
||||
'all_values': all_values,
|
||||
'values': values,
|
||||
'is_extension': is_extension,
|
||||
'usercounts': usercounts,
|
||||
'num_users': num_users,
|
||||
})
|
||||
|
||||
def report_opengl_devices(request, selected):
|
||||
reports = get_hwdetect_reports()
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('''
|
||||
SELECT DISTINCT device_name
|
||||
FROM userreport_graphicsdevice
|
||||
''')
|
||||
all_devices = set(n for (n,) in cursor.fetchall())
|
||||
|
||||
all_limits = set()
|
||||
all_exts = set()
|
||||
all_devices = set()
|
||||
devices = {}
|
||||
gl_renderers = set()
|
||||
|
||||
reports = GraphicsDevice.objects.filter(device_name__in = selected)
|
||||
for report in reports:
|
||||
json = report.data_json()
|
||||
if json is None:
|
||||
continue
|
||||
|
||||
device = report.gl_device_identifier()
|
||||
all_devices.add(device)
|
||||
if device not in selected:
|
||||
continue
|
||||
|
||||
exts = report.gl_extensions()
|
||||
exts = frozenset(e.name for e in report.graphicsextension_set.all())
|
||||
all_exts |= exts
|
||||
|
||||
limits = report.gl_limits()
|
||||
limits = dict((l.name, l.value) for l in report.graphicslimit_set.all())
|
||||
all_limits |= set(limits.keys())
|
||||
|
||||
devices.setdefault(hashabledict({'device': report.gl_device_identifier(), 'os': report.os()}), {}).setdefault((hashabledict(limits), exts), set()).add(report.gl_driver())
|
||||
devices.setdefault(hashabledict({'device': report.device_name, 'os': report.os}), {}).setdefault((hashabledict(limits), exts), set()).add(report.driver)
|
||||
|
||||
gl_renderers.add(report.renderer)
|
||||
|
||||
if len(selected) == 1 and len(devices) == 0:
|
||||
raise Http404
|
||||
@ -309,6 +352,7 @@ def report_opengl_devices(request, selected):
|
||||
'all_exts': all_exts,
|
||||
'all_devices': all_devices,
|
||||
'devices': distinct_devices,
|
||||
'gl_renderers': gl_renderers,
|
||||
})
|
||||
|
||||
def report_opengl_device(request, device):
|
||||
@ -316,3 +360,55 @@ def report_opengl_device(request, device):
|
||||
|
||||
def report_opengl_device_compare(request):
|
||||
return report_opengl_devices(request, request.GET.getlist('d'))
|
||||
|
||||
|
||||
|
||||
def report_usercount(request):
|
||||
reports = UserReport.objects.raw('''
|
||||
SELECT id, upload_date, user_id_hash
|
||||
FROM userreport_userreport
|
||||
ORDER BY upload_date
|
||||
''')
|
||||
|
||||
users_by_date = {}
|
||||
|
||||
for report in reports:
|
||||
t = report.upload_date.date() # group by day
|
||||
users_by_date.setdefault(t, set()).add(report.user_id_hash)
|
||||
|
||||
seen_users = set()
|
||||
data_scatter = ([], [], [])
|
||||
for date,users in sorted(users_by_date.items()):
|
||||
data_scatter[0].append(date)
|
||||
data_scatter[1].append(len(users))
|
||||
data_scatter[2].append(len(users - seen_users))
|
||||
seen_users |= users
|
||||
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.dates import DateFormatter
|
||||
import matplotlib.artist
|
||||
|
||||
fig = Figure(figsize=(12,6))
|
||||
|
||||
ax = fig.add_subplot(111)
|
||||
fig.subplots_adjust(left = 0.08, right = 0.95, top = 0.95, bottom = 0.2)
|
||||
|
||||
ax.plot(data_scatter[0], data_scatter[1], marker='o')
|
||||
ax.plot(data_scatter[0], data_scatter[2], marker='o')
|
||||
|
||||
ax.legend(('Total users', 'New users'), 'upper left', frameon=False)
|
||||
matplotlib.artist.setp(ax.get_legend().get_texts(), fontsize='small')
|
||||
|
||||
ax.set_ylabel('Number of users per day')
|
||||
|
||||
for label in ax.get_xticklabels():
|
||||
label.set_rotation(90)
|
||||
label.set_fontsize(9)
|
||||
|
||||
ax.xaxis.set_major_formatter(DateFormatter('%Y-%m-%d'))
|
||||
|
||||
canvas = FigureCanvas(fig)
|
||||
response = HttpResponse(content_type = 'image/png')
|
||||
canvas.print_png(response, dpi=80)
|
||||
return response
|
||||
|
@ -1,14 +1,19 @@
|
||||
from userreport.models import UserReport
|
||||
from userreport.models import UserReport, UserReport_hwdetect
|
||||
|
||||
from django.http import HttpResponse, HttpResponseForbidden
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
from django.core.paginator import Paginator, InvalidPage, EmptyPage
|
||||
from django.db.models import Q
|
||||
from django.utils import simplejson
|
||||
|
||||
import re
|
||||
import numpy
|
||||
|
||||
class hashabledict(dict):
|
||||
def __hash__(self):
|
||||
return hash(tuple(sorted(self.items())))
|
||||
|
||||
def render_reports(request, reports, template, args):
|
||||
paginator = Paginator(reports, args.get('pagesize', 100))
|
||||
try:
|
||||
@ -58,11 +63,12 @@ def report_performance(request):
|
||||
reports = UserReport.objects.order_by('upload_date')
|
||||
reports = reports.filter(
|
||||
Q(data_type = 'hwdetect', data_version__gte = 5) |
|
||||
Q(data_type = 'profile', data_version__gte = 1)
|
||||
Q(data_type = 'profile', data_version__gte = 2)
|
||||
)
|
||||
#reports = reports[:500]
|
||||
|
||||
def summarise_hwdetect(report):
|
||||
json = report.data_json()
|
||||
json = report.data_json_nocache()
|
||||
return {
|
||||
'cpu_identifier': json['cpu_identifier'],
|
||||
'device': report.gl_device_identifier(),
|
||||
@ -73,25 +79,46 @@ def report_performance(request):
|
||||
}
|
||||
|
||||
def summarise_profile(report):
|
||||
json = report.data_json()
|
||||
json = report.data_json_nocache()
|
||||
if 'map' in json:
|
||||
mapname = json['map']
|
||||
else:
|
||||
mapname = 'unknown' # e.g. random maps
|
||||
msecs = None
|
||||
shadows = False
|
||||
fancywater = False
|
||||
for name,table in json['profiler'].items():
|
||||
m = re.match(r'Profiling Information for: root \(Time in node: (\d+\.\d+) msec/frame\)', name)
|
||||
if m:
|
||||
#msecs = m.group(1)
|
||||
try:
|
||||
if report.data_version == 1:
|
||||
msecs = float(table['render']['msec/frame'])
|
||||
else:
|
||||
msecs = float(table['data']['render'][2])
|
||||
msecs = float(table['data']['render'][2])
|
||||
except KeyError:
|
||||
pass
|
||||
# TODO: get the render time?
|
||||
# TODO: get shadow, water settings
|
||||
|
||||
try:
|
||||
if float(table['data']['render'][0]['render submissions'][0]['render shadow map'][2]):
|
||||
shadows = True
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
if float(table['data']['render'][0]['render submissions'][0]['render reflections'][2]):
|
||||
fancywater = True
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
if msecs is None:
|
||||
return None
|
||||
|
||||
options = []
|
||||
if shadows: options.append('S')
|
||||
if fancywater: options.append('W')
|
||||
|
||||
return {
|
||||
'msecs': msecs,
|
||||
'map': json['map'],
|
||||
'map': mapname,
|
||||
'time': json['time'],
|
||||
'options': '[%s]' % '+'.join(options),
|
||||
# 'json': json
|
||||
}
|
||||
|
||||
@ -102,17 +129,24 @@ def report_performance(request):
|
||||
last_hwdetect[report.user_id_hash] = summarise_hwdetect(report.downcast())
|
||||
elif report.data_type == 'profile':
|
||||
if report.user_id_hash in last_hwdetect:
|
||||
profiles.append([report.user_id_hash, last_hwdetect[report.user_id_hash], summarise_profile(report)])
|
||||
hwdetect = last_hwdetect[report.user_id_hash]
|
||||
if hwdetect['build_debug']:
|
||||
continue
|
||||
prof = summarise_profile(report)
|
||||
if prof is not None:
|
||||
profiles.append([report.user_id_hash, hwdetect, prof])
|
||||
|
||||
datapoints = {}
|
||||
for user,hwdetect,profile in profiles:
|
||||
if hwdetect['build_debug']:
|
||||
if profile['map'] != 'Death Canyon':
|
||||
continue
|
||||
if profile['time'] != 5:
|
||||
continue
|
||||
# if profile['time'] != 60:
|
||||
# continue
|
||||
if profile['msecs'] is None or float(profile['msecs']) == 0:
|
||||
continue
|
||||
datapoints.setdefault(hwdetect['device'], []).append(profile['msecs'])
|
||||
fps = 1000.0 / profile['msecs']
|
||||
title = '%s %s' % (hwdetect['device'], profile['options'])
|
||||
datapoints.setdefault(title, []).append(fps)
|
||||
|
||||
# return render_to_response('reports/performance.html', {'data': datapoints, 'reports': profiles})
|
||||
|
||||
@ -120,6 +154,8 @@ def report_performance(request):
|
||||
-numpy.median(v)
|
||||
)
|
||||
|
||||
print "# %d datapoints" % sum(len(v) for k,v in sorted_datapoints)
|
||||
|
||||
data_boxplot = [ v for k,v in sorted_datapoints ]
|
||||
data_scatter = ([], [])
|
||||
for i in range(len(data_boxplot)):
|
||||
@ -130,21 +166,110 @@ def report_performance(request):
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
fig = Figure(figsize=(16,16))
|
||||
fig = Figure(figsize=(16, 0.25*len(datapoints.items())))
|
||||
|
||||
ax = fig.add_subplot(111)
|
||||
fig.subplots_adjust(left = 0.05, right = 0.99, top = 0.99, bottom = 0.2)
|
||||
fig.subplots_adjust(left = 0.22, right = 0.98, top = 0.98, bottom = 0.05)
|
||||
|
||||
ax.boxplot(data_boxplot, sym = '')
|
||||
ax.scatter(data_scatter[0], data_scatter[1], marker='x')
|
||||
ax.grid(True)
|
||||
|
||||
ax.set_ylim(1, 10000)
|
||||
ax.set_yscale('log')
|
||||
ax.set_ylabel('Render time (msecs)')
|
||||
ax.boxplot(data_boxplot, vert = 0, sym = '')
|
||||
ax.scatter(data_scatter[1], data_scatter[0], marker='x')
|
||||
|
||||
ax.set_xticklabels([k for k,v in sorted_datapoints], rotation=90, fontsize=8)
|
||||
ax.set_xlim(0.1, 1000)
|
||||
ax.set_xscale('log')
|
||||
ax.set_xlabel('Framerate (fps)')
|
||||
|
||||
ax.set_yticklabels([k for k,v in sorted_datapoints], fontsize=8)
|
||||
ax.set_ylabel('Device [options: Shadows + Water reflections]')
|
||||
|
||||
canvas = FigureCanvas(fig)
|
||||
response = HttpResponse(content_type = 'image/png')
|
||||
canvas.print_png(response, dpi=80)
|
||||
return response
|
||||
|
||||
def report_ram(request):
|
||||
reports = UserReport.objects
|
||||
reports = reports.filter(data_type = 'hwdetect', data_version__gte = 1)
|
||||
|
||||
counts = {}
|
||||
for report in reports:
|
||||
#if not report.data_json()['os_linux']: continue
|
||||
ram = report.data_json()['ram_total']
|
||||
counts.setdefault(ram, set()).add(report.user_id_hash)
|
||||
|
||||
datapoints = []
|
||||
accum = 0
|
||||
for size,count in sorted(counts.items()):
|
||||
accum += len(count)
|
||||
datapoints.append((size, accum))
|
||||
|
||||
print "\n".join(repr(r) for r in datapoints)
|
||||
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
fig = Figure(figsize=(16, 10))
|
||||
|
||||
ax = fig.add_subplot(111)
|
||||
fig.subplots_adjust(left = 0.05, right = 0.98, top = 0.98, bottom = 0.05)
|
||||
|
||||
ax.grid(True)
|
||||
|
||||
ax.plot([ d[0] for d in datapoints ], [ 100*(1-float(d[1])/accum) for d in datapoints ])
|
||||
|
||||
ax.set_xticks([0, 256, 512] + [1024*n for n in range(1, 9)])
|
||||
ax.set_xlim(0, 8192)
|
||||
ax.set_xlabel('RAM (megabytes)')
|
||||
|
||||
ax.set_yticks(range(0, 101, 5))
|
||||
ax.set_ylim(0, 100)
|
||||
ax.set_ylabel('Cumulative percentage of users')
|
||||
|
||||
canvas = FigureCanvas(fig)
|
||||
response = HttpResponse(content_type = 'image/png')
|
||||
canvas.print_png(response, dpi=80)
|
||||
return response
|
||||
|
||||
def report_os(request):
|
||||
reports = UserReport_hwdetect.objects
|
||||
reports = reports.filter(data_type = 'hwdetect', data_version__gte = 1)
|
||||
|
||||
counts = {}
|
||||
for report in reports:
|
||||
json = report.data_json()
|
||||
if 'linux_release' in json:
|
||||
counts.setdefault(repr(json['linux_release']), set()).add(report.user_id_hash)
|
||||
os = report.os()
|
||||
counts.setdefault(os, set()).add(report.user_id_hash)
|
||||
|
||||
out = ''
|
||||
for c in sorted(counts.items(), key = lambda c: len(c[1])):
|
||||
out += '%d %s\n' % (len(c[1]), c[0])
|
||||
|
||||
return HttpResponse(out, content_type = 'text/plain')
|
||||
|
||||
def report_hwdetect_test_data(request):
|
||||
reports = UserReport_hwdetect.objects
|
||||
reports = reports.filter(data_type = 'hwdetect', data_version__gte = 1)
|
||||
|
||||
data = set()
|
||||
for report in reports:
|
||||
json = report.data_json_nocache()
|
||||
relevant = {
|
||||
'os_unix': json['os_unix'],
|
||||
'os_linux': json['os_linux'],
|
||||
'os_macosx': json['os_macosx'],
|
||||
'os_win': json['os_win'],
|
||||
'gfx_card': json['gfx_card'],
|
||||
'gfx_drv_ver': json['gfx_drv_ver'],
|
||||
'gfx_mem': json['gfx_mem'],
|
||||
'GL_VENDOR': json['GL_VENDOR'],
|
||||
'GL_RENDERER': json['GL_RENDERER'],
|
||||
'GL_VERSION': json['GL_VERSION'],
|
||||
'GL_EXTENSIONS': json['GL_EXTENSIONS'],
|
||||
}
|
||||
data.add(hashabledict(relevant))
|
||||
|
||||
json = simplejson.dumps(list(data), indent=1, sort_keys=True)
|
||||
return HttpResponse('var hwdetectTestData = %s' % json, content_type = 'text/plain')
|
||||
|
Loading…
Reference in New Issue
Block a user