Latest version of user-report server code

This was SVN commit r9393.
This commit is contained in:
Ykkrosh 2011-05-02 15:47:12 +00:00
parent 0b996bbe75
commit 674eaa1283
15 changed files with 813 additions and 164 deletions

View File

@ -0,0 +1,8 @@
#!/usr/bin/env python
import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
from userreport import maint
maint.collect_graphics()

View 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

View File

@ -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,
},
}
}

View File

@ -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)

View 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"

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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 %}

View File

@ -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&#X0040;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>

View File

@ -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

View File

@ -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'),
)

View File

@ -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'),
)

View File

@ -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

View File

@ -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')