diff --git a/source/tools/webservices/__init__.py b/source/tools/webservices/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/source/tools/webservices/manage.py b/source/tools/webservices/manage.py
new file mode 100644
index 0000000000..5e78ea979e
--- /dev/null
+++ b/source/tools/webservices/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/source/tools/webservices/settings.py b/source/tools/webservices/settings.py
new file mode 100644
index 0000000000..95d74b597f
--- /dev/null
+++ b/source/tools/webservices/settings.py
@@ -0,0 +1,120 @@
+from settings_local import DEBUG, ADMINS, DATABASES, SECRET_KEY, WFGSITE_ROOT
+
+TEMPLATE_DEBUG = DEBUG
+
+MANAGERS = ADMINS
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'UTC'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = False
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = False
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/media/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = ''
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/static/"
+STATIC_ROOT = ''
+
+# URL that handles the static files served from STATIC_ROOT.
+# Example: "http://static.lawrence.com/", "http://example.com/static/"
+STATIC_URL = '/static/'
+
+# URL prefix for admin media -- CSS, JavaScript and images.
+# Make sure to use a trailing slash.
+# Examples: "http://foo.com/static/admin/", "/static/admin/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
+
+# A list of locations of additional static files
+STATICFILES_DIRS = ()
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ '%s/userreport/templates' % WFGSITE_ROOT,
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'django.contrib.admin',
+
+ 'userreport',
+)
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+#LOGGING = {
+# 'version': 1,
+# 'disable_existing_loggers': False,
+# 'handlers': {
+# 'mail_admins': {
+# 'level': 'ERROR',
+# 'class': 'django.utils.log.AdminEmailHandler'
+# }
+# },
+# 'loggers': {
+# 'django.request':{
+# 'handlers': ['mail_admins'],
+# 'level': 'ERROR',
+# 'propagate': True,
+# },
+# }
+#}
diff --git a/source/tools/webservices/settings_local.EXAMPLE.py b/source/tools/webservices/settings_local.EXAMPLE.py
new file mode 100644
index 0000000000..49432fc340
--- /dev/null
+++ b/source/tools/webservices/settings_local.EXAMPLE.py
@@ -0,0 +1,20 @@
+# Fill in this file and save as settings_local.py
+
+DEBUG = True
+
+ADMINS = (
+ ('Your Name', 'you@example.com'),
+)
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.mysql',
+ 'NAME': 'wfgsite',
+ 'USER': 'wfgsite',
+ 'PASSWORD': '################',
+ }
+}
+
+SECRET_KEY = '##################################################'
+
+WFGSITE_ROOT = '/var/####/wfgsite'
diff --git a/source/tools/webservices/setup.txt b/source/tools/webservices/setup.txt
new file mode 100644
index 0000000000..e2c1f3f150
--- /dev/null
+++ b/source/tools/webservices/setup.txt
@@ -0,0 +1,28 @@
+This has been tested with Django 1.3 Beta 1.
+
+To set up the database, run commands like:
+
+ In MySQL:
+ create database wfgservices character set utf8;
+ grant create,alter,index,select,insert,update,delete on wfgservices.* to 'wfgservices'@'localhost' identified by '[PASSWORD]';
+ In shell:
+ python manage.py syncdb
+
+Copy settings_local.EXAMPLE.py to settings_local.py and fill in the details.
+
+Set up Apache with some options kind of like:
+
+ WSGIScriptAlias / /var/www/wherever/django.wsgi
+
+ SetOutputFilter DEFLATE
+ Options None
+
+
+ AuthType Basic
+ AuthName "WFG login"
+ AuthUserFile ...
+ Require valid-user
+
+ Alias /static/admin/ /usr/lib/python2.5/site-packages/django/contrib/admin/media/
+ Alias /robots.txt /var/www/wherever/robots.txt
+ Alias /favicon.ico /var/www/wherever/favicon.ico
diff --git a/source/tools/webservices/urls.py b/source/tools/webservices/urls.py
new file mode 100644
index 0000000000..871f34cc2e
--- /dev/null
+++ b/source/tools/webservices/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls.defaults import *
+
+from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ (r'^$', 'userreport.views.index'),
+ (r'^report/', include('userreport.urls')),
+ (r'^private/', include('userreport.urls_private')),
+ (r'^admin/', include(admin.site.urls)),
+)
diff --git a/source/tools/webservices/userreport/__init__.py b/source/tools/webservices/userreport/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/source/tools/webservices/userreport/admin.py b/source/tools/webservices/userreport/admin.py
new file mode 100644
index 0000000000..ef011ec446
--- /dev/null
+++ b/source/tools/webservices/userreport/admin.py
@@ -0,0 +1,17 @@
+from userreport.models import UserReport
+from django.contrib import admin
+
+class UserReportAdmin(admin.ModelAdmin):
+
+ readonly_fields = ['uploader', 'user_id_hash', 'upload_date', 'generation_date', 'data_type', 'data_version', 'data']
+ fieldsets = [
+ ('User', {'fields': ['uploader', 'user_id_hash']}),
+ ('Dates', {'fields': ['upload_date', 'generation_date']}),
+ (None, {'fields': ['data_type', 'data_version', 'data']}),
+ ]
+ list_display = ('uploader', 'user_id_hash', 'data_type', 'data_version', 'upload_date', 'generation_date')
+ list_filter = ['upload_date', 'generation_date', 'data_type']
+ search_fields = ['=uploader', '=user_id_hash']
+ date_hierarchy = 'upload_date'
+
+admin.site.register(UserReport, UserReportAdmin)
diff --git a/source/tools/webservices/userreport/models.py b/source/tools/webservices/userreport/models.py
new file mode 100644
index 0000000000..8ca1d4eb45
--- /dev/null
+++ b/source/tools/webservices/userreport/models.py
@@ -0,0 +1,69 @@
+from django.db import models
+from django.utils import simplejson
+
+class UserReport(models.Model):
+
+ uploader = models.IPAddressField(editable = False)
+
+ # Hex SHA-1 digest of user's reported ID
+ # (The hashing means that publishing the database won't let people upload
+ # faked reports under someone else's user ID, and also ensures a simple
+ # consistent structure)
+ user_id_hash = models.CharField(max_length = 40, db_index = True, editable = False)
+
+ # When the server received the upload
+ upload_date = models.DateTimeField(auto_now_add = True, db_index = True, editable = False)
+
+ # When the user claims to have generated the report
+ generation_date = models.DateTimeField(editable = False)
+
+ data_type = models.CharField(max_length = 16, db_index = True, editable = False)
+
+ data_version = models.IntegerField(editable = False)
+
+ data = models.TextField(editable = False)
+
+ def data_json(self):
+ if not hasattr(self, 'cached_json'):
+ try:
+ self.cached_json = simplejson.loads(self.data)
+ except:
+ self.cached_json = None
+ return self.cached_json
+
+class UserReport_hwdetect(UserReport):
+
+ class Meta:
+ proxy = True
+
+ def os(self):
+ os = 'Unknown'
+ json = self.data_json()
+ if json:
+ if json['os_win']:
+ os = 'Windows'
+ elif json['os_linux']:
+ os = 'Linux'
+ elif json['os_macosx']:
+ os = 'OS X'
+ return os
+
+ def gl_extensions(self):
+ json = self.data_json()
+ if json is None or 'GL_EXTENSIONS' not in json:
+ return None
+ return frozenset(json['GL_EXTENSIONS'].strip().split(' '))
+
+ def gl_limits(self):
+ json = self.data_json()
+ if json is None:
+ return None
+
+ limits = {}
+ for (k, v) in json.items():
+ if not k.startswith('GL_'):
+ continue
+ if k in ('GL_RENDERER', 'GL_VENDOR', 'GL_EXTENSIONS'):
+ continue
+ limits[k] = v
+ return limits
diff --git a/source/tools/webservices/userreport/templates/404.html b/source/tools/webservices/userreport/templates/404.html
new file mode 100644
index 0000000000..9b4a0efd9f
--- /dev/null
+++ b/source/tools/webservices/userreport/templates/404.html
@@ -0,0 +1,4 @@
+
+
+
404
+File not found.
diff --git a/source/tools/webservices/userreport/templates/index.html b/source/tools/webservices/userreport/templates/index.html
new file mode 100644
index 0000000000..12843ce750
--- /dev/null
+++ b/source/tools/webservices/userreport/templates/index.html
@@ -0,0 +1,15 @@
+
+
+0 A.D. report service
+
+
+This site collects opt-in automatic feedback from players of 0 A.D.
+
+
Published data:
+OpenGL capabilities.
+CPU features.
diff --git a/source/tools/webservices/userreport/templates/reports/base.html b/source/tools/webservices/userreport/templates/reports/base.html
new file mode 100644
index 0000000000..c6794d96f0
--- /dev/null
+++ b/source/tools/webservices/userreport/templates/reports/base.html
@@ -0,0 +1,61 @@
+
+
+
{% block title %}Report{% endblock %}
+
+
+{% block heading %}Report{% endblock %}
+
+{% block content %}{% endblock %}
+
+{% if report_page %}{% if report_page.has_previous or report_page.has_next %}
+
+{% endif %}{% endif %}
\ No newline at end of file
diff --git a/source/tools/webservices/userreport/templates/reports/cpu.html b/source/tools/webservices/userreport/templates/reports/cpu.html
new file mode 100644
index 0000000000..8a48b75dd9
--- /dev/null
+++ b/source/tools/webservices/userreport/templates/reports/cpu.html
@@ -0,0 +1,52 @@
+{% extends "reports/base.html" %}
+{% load report_tags %}
+
+{% block css %}
+table.profile td {
+ line-height: inherit;
+ border-bottom: inherit;
+ padding: 0 1em 0 0.5em;
+}
+.treemarker {
+ color: #666;
+ font-family: monospace;
+ white-space: pre;
+}
+{% endblock %}
+
+{% block title %}
+CPU capabilities report
+{% endblock %}
+
+{% block heading %}
+CPU capabilities
+{% endblock %}
+
+{% block content %}
+Based on data submitted by players of 0 A.D.
+
+See the index page for more stuff.
+
+
+
+ OS
+ | Identifier
+ | V/M/F
+ | Freq
+ | Num procs
+ | Caches (data/instruction/unified)
+ | TLBs
+ | Feature bits
+{% for cpu,users in cpus|sortedcpuitems %}
+ |
+ {{ cpu.os }}
+ | {{ cpu.cpu_identifier }}
+ | {{ cpu.x86_vendor }}/{{ cpu.x86_model }}/{{ cpu.x86_family }}
+ | {% if cpu.cpu_frequency = -1 %}?{% else %}{{ cpu.cpu_frequency|cpufreqformat }}{% endif %}
+ | {{ cpu.cpu_numpackages }}×{{ cpu.cpu_coresperpackage }}×{{ cpu.cpu_logicalpercore }} = {{ cpu.cpu_numprocs }}
+ | {{ cpu.caches|join:" " }}
+ | {{ cpu.tlbs|join:" " }}
+ | {% for cap in cpu.caps|sort %}{% if cap in x86_cap_descs %}{{ cap }}{% else %}{{ cap }}{% endif %} {% endfor %}
+{% endfor %}
+ |
+{% endblock %}
diff --git a/source/tools/webservices/userreport/templates/reports/hwdetect.html b/source/tools/webservices/userreport/templates/reports/hwdetect.html
new file mode 100644
index 0000000000..c213221b79
--- /dev/null
+++ b/source/tools/webservices/userreport/templates/reports/hwdetect.html
@@ -0,0 +1,17 @@
+{% extends "reports/base.html" %}
+{% load report_tags %}
+
+{% block content %}
+
+
+ Received
+ | User
+ | Data
+{% for report in report_page.object_list %}
+ |
+ {{ report.upload_date|date:"Y-m-d" }} {{ report.upload_date|date:"H:i:s" }}
+ | {{ report.user_id_hash|slice:"0:8" }}
+ | {{ report.data|prettify_json }}
+{% endfor %}
+ |
+{% endblock %}
\ No newline at end of file
diff --git a/source/tools/webservices/userreport/templates/reports/message.html b/source/tools/webservices/userreport/templates/reports/message.html
new file mode 100644
index 0000000000..7432f38f2f
--- /dev/null
+++ b/source/tools/webservices/userreport/templates/reports/message.html
@@ -0,0 +1,16 @@
+{% extends "reports/base.html" %}
+
+{% block content %}
+
+
+ Received
+ | User
+ | Message
+{% for report in report_page.object_list %}
+ |
+ {{ report.upload_date|date:"Y-m-d" }} {{ report.upload_date|date:"H:i:s" }}
+ | {{ report.user_id_hash|slice:"0:8" }}
+ | {{ report.data }}
+{% endfor %}
+ |
+{% endblock %}
\ No newline at end of file
diff --git a/source/tools/webservices/userreport/templates/reports/opengl_device.html b/source/tools/webservices/userreport/templates/reports/opengl_device.html
new file mode 100644
index 0000000000..76ea7c5033
--- /dev/null
+++ b/source/tools/webservices/userreport/templates/reports/opengl_device.html
@@ -0,0 +1,94 @@
+{% extends "reports/base.html" %}
+{% load report_tags %}
+{% load cycle %}
+
+{% block css %}
+th.alt, td.alt { background: #f6f6f6; }
+td.true { background: #3f3; }
+td.false { background: #f33; }
+td.true.alt { background: #2e2; }
+td.false.alt { background: #e22; }
+
+.device-status ul {
+ margin: 0;
+ padding: 0;
+}
+
+.device-status li {
+ list-style: none;
+}
+{% endblock %}
+
+{% block title %}
+OpenGL capabilities report: {{ selected|join:' vs. ' }}
+{% endblock %}
+
+{% block heading %}
+OpenGL capabilities report
+{% endblock %}
+
+{% block content %}
+
+(Back to index page.)
+
+The table here shows the features reported for the following devices:
+
+{% for device in selected %}- {{ device }}{% endfor %}
+
+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.
+
+Green cells indicate supported extensions; red cells indicate non-supported extensions.
+
+
+
+
+
+{% for ext in all_exts %}
+
+ {% if not forloop.counter0|mod:30 %}
+ |
+ {% for device in devices %}
+ |
+ {{ device.0.renderer }} ({{ device.0.os }}):
+ {% for driver in device.1 %}- {{ driver }}{% endfor %}
+ {% endfor %}
+ {% endif %}
+
+ |
---|
+ {{ ext }} (spec)
+ {% for device in devices %}
+ |
+ {% endfor %}
+{% endfor %}
+
+{% for limit in all_limits %}
+ {% if not forloop.counter|mod:30 %}
+ |
+ {% for device in devices %}
+ |
+ {{ device.0.renderer }} ({{ device.0.os }}):
+ {% for driver in device.1 %}- {{ driver }}{% endfor %}
+ {% endfor %}
+ {% endif %}
+
+ |
---|
+ {{ limit|prettify_gl_title }}
+ {% for device in devices %}
+ | {{ device.2.0|dictget:limit }}
+ {% endfor %}
+{% endfor %}
+ |
+
+Compare with other devices
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/source/tools/webservices/userreport/templates/reports/opengl_feature.html b/source/tools/webservices/userreport/templates/reports/opengl_feature.html
new file mode 100644
index 0000000000..7e86b2e67a
--- /dev/null
+++ b/source/tools/webservices/userreport/templates/reports/opengl_feature.html
@@ -0,0 +1,121 @@
+{% extends "reports/base.html" %}
+{% load report_tags %}
+{% load cycle %}
+
+{% block css %}
+ul {
+ margin: 0;
+}
+
+.support-status .r {
+ font-weight: bold;
+}
+
+.support-status ul {
+ margin: 0;
+ padding: 0;
+}
+
+.support-status li {
+ list-style: none;
+}
+
+tr.head {
+ background: #ddd;
+}
+
+tr.data:hover {
+ background: #ddd;
+}
+{% endblock %}
+
+{% block title %}
+OpenGL capabilities report: {{ feature }}
+{% endblock %}
+
+{% block heading %}
+OpenGL capabilities report: {{ feature }}
+{% endblock %}
+
+{% block content %}
+
+(Back to index page.)
+
+{% if is_extension %}
+
+View specification for {{ feature }}.
+
+
+
+
+ Supported by:
+
+ |
+ Vendor
+ | Renderer
+ | OS
+ | Driver versions
+
+{% for device in values.true|sorteddeviceitems %}
+ |
+{{ device.0.vendor }}
+ | {{ device.0.renderer }}
+ | {{ device.0.os }}
+ |
+{% for driver in device.1|sort %}- {{ driver }}{% endfor %}
+
+{% endfor %}
+
+ |
+ Not supported by:
+
+ |
+ Vendor
+ | Renderer
+ | OS
+ | Driver versions
+
+{% for device in values.false|sorteddeviceitems %}
+ |
+{{ device.0.vendor }}
+ | {{ device.0.renderer }}
+ | {{ device.0.os }}
+ |
+{% for driver in device.1|sort %}- {{ driver }}{% endfor %}
+
+{% endfor %}
+
+ |
+
+{% else %}
+
+
+
+ {% for val in values.keys|sortreversed %}
+
+
+ Value: {{ val|default_if_none:"Unsupported/unknown" }}
+
+ |
+ Vendor
+ | Renderer
+ | OS
+ | Driver versions
+
+{% for device in values|dictget:val|sorteddeviceitems %}
+ |
+{{ device.0.vendor }}
+ | {{ device.0.renderer }}
+ | {{ device.0.os }}
+ |
+{% for driver in device.1|sort %}- {{ driver }}{% endfor %}
+
+{% endfor %}
+
+ {% endfor %}
+
+ |
+
+{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/source/tools/webservices/userreport/templates/reports/opengl_index.html b/source/tools/webservices/userreport/templates/reports/opengl_index.html
new file mode 100644
index 0000000000..3bc8db16d2
--- /dev/null
+++ b/source/tools/webservices/userreport/templates/reports/opengl_index.html
@@ -0,0 +1,102 @@
+{% extends "reports/base.html" %}
+{% load report_tags %}
+
+{% block css %}
+
+.col {
+ float: left;
+}
+
+.progress {
+ font-size: 10px;
+ font-weight: bold;
+ display: inline-block;
+ background: #f99;
+ width: 40px;
+ height: 13px;
+}
+.progress .bar {
+ display: inline-block;
+ position: absolute;
+ background: #3f3;
+ height: 13px;
+ z-index: 0;
+}
+
+li:hover {
+ background: #ddd;
+}
+
+{% endblock %}
+
+{% block title %}
+OpenGL capabilities database - index
+{% endblock %}
+
+{% block heading %}
+OpenGL capabilities database
+{% endblock %}
+
+{% block content %}
+
+
+
+Based on data submitted by players of 0 A.D.
+
Browse the data here, or download as JSON.
+Feel free to do whatever you want with the data.
+See the index page for more stuff.
+
+
+
Extension support
+Sort by
+
% support /
+
name.
+
+{% for ext in all_exts %}
+-
+{% widthratio ext_devices|dictget:ext|length all_devices|length 100 %}%
+(spec)
+{{ ext }}
+{% endfor %}
+
+
+
Implementation limits
+
+{% for limit in all_limits %}
+- {{ limit }}
+{% endfor %}
+
+
+
+
+
Device details
+
+{% for device in all_devices %}
+- {{ device }}
+{% endfor %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/source/tools/webservices/userreport/templates/reports/profile.html b/source/tools/webservices/userreport/templates/reports/profile.html
new file mode 100644
index 0000000000..e0a66f3ec5
--- /dev/null
+++ b/source/tools/webservices/userreport/templates/reports/profile.html
@@ -0,0 +1,41 @@
+{% extends "reports/base.html" %}
+{% load report_tags %}
+
+{% block css %}
+table.profile td {
+ line-height: inherit;
+ border-bottom: inherit;
+ padding: 0 1em 0 0.5em;
+}
+.treemarker {
+ color: #666;
+ font-family: monospace;
+ white-space: pre;
+}
+{% endblock %}
+
+{% block content %}
+
+
+ Received
+ | User
+ | Profiler
+{% for report in report_page.object_list %}
+{% with json=report.data_json %}
+ |
+ {{ report.upload_date|date:"Y-m-d" }} {{ report.upload_date|date:"H:i:s" }}
+ | {{ report.user_id_hash|slice:"0:8" }}
+ |
+ Time: {{ json.time }}
+ Map: {{ json.map }}
+{% for name,table in json.profiler.items %}
+ {{ name }}
+
+{{ table|format_profile }}
+
+{% endfor %}
+
+{% endwith %}
+{% endfor %}
+ |
+{% endblock %}
\ No newline at end of file
diff --git a/source/tools/webservices/userreport/templates/reports/user.html b/source/tools/webservices/userreport/templates/reports/user.html
new file mode 100644
index 0000000000..2e8df66f81
--- /dev/null
+++ b/source/tools/webservices/userreport/templates/reports/user.html
@@ -0,0 +1,31 @@
+{% extends "reports/base.html" %}
+{% load report_tags %}
+
+{% block css %}
+td.rawdata {
+ white-space: pre;
+}
+{% endblock %}
+
+{% block title %}
+User {{ user }}
+{% endblock %}
+
+{% block heading %}
+All reports from user {{ user }}
+{% endblock %}
+
+{% block content %}
+
+
+ Received
+ | Type
+ | Raw data
+{% for report in report_page.object_list %}
+ |
+ {{ report.upload_date|date:"Y-m-d" }} {{ report.upload_date|date:"H:i:s" }}
+ | {{ report.data_type }} (v{{ report.data_version }})
+ | {{ report.data|prettify_json }}
+{% endfor %}
+ |
+{% endblock %}
\ No newline at end of file
diff --git a/source/tools/webservices/userreport/templatetags/__init__.py b/source/tools/webservices/userreport/templatetags/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/source/tools/webservices/userreport/templatetags/cycle.py b/source/tools/webservices/userreport/templatetags/cycle.py
new file mode 100644
index 0000000000..5f36bb9aa4
--- /dev/null
+++ b/source/tools/webservices/userreport/templatetags/cycle.py
@@ -0,0 +1,99 @@
+# http://code.djangoproject.com/attachment/ticket/5908/cycle.py
+
+from django.utils.translation import ungettext, ugettext as _
+from django.utils.encoding import force_unicode
+from django import template
+from django.template import defaultfilters
+from django.template import Node, Variable
+from django.conf import settings
+from itertools import cycle as itertools_cycle
+
+register = template.Library()
+
+
+
+class SafeCycleNode(Node):
+ def __init__(self, cyclevars, variable_name=None):
+ self.cyclevars = cyclevars
+ self.cycle_iter = itertools_cycle(cyclevars)
+ self.variable_name = variable_name
+
+ def render(self, context):
+ if context.has_key('forloop'):
+ if not context.get(self):
+ context[self] = True
+ self.cycle_iter = itertools_cycle(self.cyclevars)
+ value = self.cycle_iter.next()
+ value = Variable(value).resolve(context)
+ if self.variable_name:
+ context[self.variable_name] = value
+ return value
+
+
+
+#@register.tag
+def safe_cycle(parser, token):
+ """
+ Cycles among the given strings each time this tag is encountered.
+
+ Within a loop, cycles among the given strings each time through
+ the loop::
+
+ {% for o in some_list %}
+
+ ...
+
+ {% endfor %}
+
+ Outside of a loop, give the values a unique name the first time you call
+ it, then use that name each sucessive time through::
+
+ ...
+ ...
+ ...
+
+ You can use any number of values, seperated by spaces. Commas can also
+ be used to separate values; if a comma is used, the cycle values are
+ interpreted as literal strings.
+ """
+
+ # Note: This returns the exact same node on each {% cycle name %} call;
+ # that is, the node object returned from {% cycle a b c as name %} and the
+ # one returned from {% cycle name %} are the exact same object. This
+ # shouldn't cause problems (heh), but if it does, now you know.
+ #
+ # Ugly hack warning: this stuffs the named template dict into parser so
+ # that names are only unique within each template (as opposed to using
+ # a global variable, which would make cycle names have to be unique across
+ # *all* templates.
+
+ args = token.split_contents()
+
+ if len(args) < 2:
+ raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
+
+ if ',' in args[1]:
+ # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
+ # case.
+ args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
+
+ if len(args) == 2:
+ # {% cycle foo %} case.
+ name = args[1]
+ if not hasattr(parser, '_namedCycleNodes'):
+ raise TemplateSyntaxError("No named cycles in template."
+ " '%s' is not defined" % name)
+ if not name in parser._namedCycleNodes:
+ raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
+ return parser._namedCycleNodes[name]
+
+ if len(args) > 4 and args[-2] == 'as':
+ name = args[-1]
+ node = SafeCycleNode(args[1:-2], name)
+ if not hasattr(parser, '_namedCycleNodes'):
+ parser._namedCycleNodes = {}
+ parser._namedCycleNodes[name] = node
+ else:
+ node = SafeCycleNode(args[1:])
+ return node
+safe_cycle = register.tag(safe_cycle)
diff --git a/source/tools/webservices/userreport/templatetags/report_tags.py b/source/tools/webservices/userreport/templatetags/report_tags.py
new file mode 100644
index 0000000000..c7daf17641
--- /dev/null
+++ b/source/tools/webservices/userreport/templatetags/report_tags.py
@@ -0,0 +1,128 @@
+# coding=utf-8
+
+from django import template
+from django.template.defaultfilters import stringfilter
+from django.utils import simplejson
+from django.utils.safestring import mark_safe
+from django.utils.html import conditional_escape
+
+register = template.Library()
+
+@register.filter
+def mod(value, arg):
+ return value % arg
+
+@register.filter
+@stringfilter
+def has_token(value, token):
+ "Returns whether a space-separated list of tokens contains a given token"
+ return token in value.split(' ')
+
+@register.filter
+@stringfilter
+def wrap_at_underscores(value):
+ return value.replace('_', '_')
+wrap_at_underscores.is_safe = True
+
+@register.filter
+@stringfilter
+def prettify_json(value):
+ try:
+ data = simplejson.loads(value)
+ return simplejson.dumps(data, indent=2, sort_keys=True)
+ except:
+ return value
+
+@register.filter
+@stringfilter
+def glext_spec_link(value):
+ c = value.split('_', 2)
+ return 'http://www.opengl.org/registry/specs/%s/%s.txt' % (c[1], c[2])
+
+@register.filter
+@stringfilter
+def prettify_gl_title(value):
+ if value.startswith('GL_FRAGMENT_PROGRAM_ARB.'):
+ return value[24:] + ' (fragment)'
+ if value.startswith('GL_VERTEX_PROGRAM_ARB.'):
+ return value[22:] + ' (vertex)'
+ return value
+
+@register.filter
+def dictget(value, key):
+ return value.get(key, '')
+
+@register.filter
+def sorteditems(value):
+ return sorted(value.items(), key = lambda (k, v): k)
+
+@register.filter
+def sorteddeviceitems(value):
+ return sorted(value.items(), key = lambda (k, v): (k['vendor'], k['renderer'], k['os'], v))
+
+@register.filter
+def sortedcpuitems(value):
+ return sorted(value.items(), key = lambda (k, v): (k['x86_vendor'], k['x86_model'], k['x86_family'], k['cpu_identifier']))
+
+@register.filter
+def cpufreqformat(value):
+ return mark_safe("%.2f GHz" % (int(value)/1000000000.0))
+
+@register.filter
+def sort(value):
+ return sorted(value)
+
+@register.filter
+def sortreversed(value):
+ return reversed(sorted(value))
+
+@register.filter
+def reverse(value):
+ return reversed(value)
+
+@register.filter
+def format_profile(table):
+ cols = set()
+ def extract_cols(t):
+ for name, row in t.items():
+ for n in row:
+ cols.add(n)
+ if 'children' in row:
+ extract_cols(row['children'])
+ extract_cols(table)
+ if 'children' in cols:
+ cols.remove('children')
+
+ if 'msec/frame' in cols:
+ cols = ('msec/frame', 'calls/frame', '%/frame', '%/parent', 'mem allocs')
+ else:
+ cols = sorted(cols)
+
+
+ out = ['']
+ for c in cols:
+ out.append(u' | %s' % conditional_escape(c))
+
+ def handle(indents, indent, t):
+ if 'msec/frame' in cols:
+ items = [d[1] for d in sorted((-float(r.get('msec/frame', '')), (n,r)) for (n,r) in t.items())]
+ else:
+ items = sorted(t.items())
+
+ item_id = 0
+ for name, row in items:
+ if item_id == len(items) - 1:
+ last = True
+ else:
+ last = False
+ item_id += 1
+
+ out.append(u' | ')
+ out.append(u'%s%s─%s╴%s' % (indent, (u'└' if last else u'├'), (u'┬' if 'children' in row else u'─'), conditional_escape(name)))
+ for c in cols:
+ out.append(u' | %s%s' % (' ' * indents, conditional_escape(row.get(c, ''))))
+ if 'children' in row:
+ handle(indents+1, indent+(u' ' if last else u'│ '), row['children'])
+ handle(0, u'', table)
+
+ return mark_safe(u'\n'.join(out))
diff --git a/source/tools/webservices/userreport/urls.py b/source/tools/webservices/userreport/urls.py
new file mode 100644
index 0000000000..f51371ac2a
--- /dev/null
+++ b/source/tools/webservices/userreport/urls.py
@@ -0,0 +1,13 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ (r'^upload/v1/$', 'userreport.views.upload'),
+
+ (r'^opengl/$', 'userreport.views.report_opengl_index'),
+ (r'^opengl/json$', 'userreport.views.report_opengl_json'),
+ (r'^opengl/feature/(?P[^/]+)$', 'userreport.views.report_opengl_feature'),
+ (r'^opengl/device/(?P.+)$', 'userreport.views.report_opengl_device'),
+ (r'^opengl/device', 'userreport.views.report_opengl_device_compare'),
+
+ (r'^cpu/$', 'userreport.views.report_cpu'),
+)
diff --git a/source/tools/webservices/userreport/urls_private.py b/source/tools/webservices/userreport/urls_private.py
new file mode 100644
index 0000000000..fa12526905
--- /dev/null
+++ b/source/tools/webservices/userreport/urls_private.py
@@ -0,0 +1,8 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ (r'^hwdetect/$', 'userreport.views_private.report_hwdetect'),
+ (r'^messages/$', 'userreport.views_private.report_messages'),
+ (r'^profile/$', 'userreport.views_private.report_profile'),
+ (r'^user/([0-9a-f]+)$', 'userreport.views_private.report_user'),
+)
diff --git a/source/tools/webservices/userreport/views.py b/source/tools/webservices/userreport/views.py
new file mode 100644
index 0000000000..9797828b1c
--- /dev/null
+++ b/source/tools/webservices/userreport/views.py
@@ -0,0 +1,314 @@
+from userreport.models import UserReport, UserReport_hwdetect
+import userreport.x86 as x86
+
+import hashlib
+import datetime
+import zlib
+
+from django.http import HttpResponseBadRequest, HttpResponse, Http404, QueryDict
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils import simplejson
+
+from django.views.decorators.csrf import csrf_exempt
+from django.views.decorators.http import require_POST
+
+class hashabledict(dict):
+ def __hash__(self):
+ return hash(tuple(sorted(self.items())))
+
+@csrf_exempt
+@require_POST
+def upload(request):
+
+ try:
+ decompressed = zlib.decompress(request.raw_post_data)
+ except zlib.error:
+ return HttpResponseBadRequest('Invalid POST data.\n', content_type = 'text/plain')
+
+ POST = QueryDict(decompressed)
+
+ try:
+ user_id = POST['user_id']
+ generation_date = datetime.datetime.utcfromtimestamp(int(POST['time']))
+ data_type = POST['type']
+ data_version = int(POST['version'])
+ data = POST['data']
+ except KeyError, e:
+ return HttpResponseBadRequest('Missing required fields.\n', content_type = 'text/plain')
+
+ uploader = request.META['REMOTE_ADDR']
+ # Fix the IP address if running via proxy on localhost
+ if uploader == '127.0.0.1':
+ try:
+ uploader = request.META['HTTP_X_FORWARDED_FOR'].split(',')[0].strip()
+ except KeyError:
+ pass
+
+ user_id_hash = hashlib.sha1(user_id).hexdigest()
+
+ report = UserReport(
+ uploader = uploader,
+ user_id_hash = user_id_hash,
+ generation_date = generation_date,
+ data_type = data_type,
+ data_version = data_version,
+ data = data
+ )
+ report.save()
+
+ return HttpResponse('OK', content_type = 'text/plain')
+
+
+def index(request):
+ return render_to_response('index.html')
+
+
+def report_cpu(request):
+ reports = UserReport_hwdetect.objects
+ reports = reports.filter(data_type = 'hwdetect', data_version__gte = 4)
+
+ all_users = set()
+ cpus = {}
+
+ for report in reports:
+ json = report.data_json()
+ if json is None:
+ continue
+
+ cpu = {}
+ for x in (
+ 'x86_vendor', 'x86_model', 'x86_family',
+ 'cpu_identifier', 'cpu_frequency',
+ 'cpu_numprocs', 'cpu_numpackages', 'cpu_coresperpackage', 'cpu_logicalpercore',
+ 'cpu_numcaches',
+ 'cpu_pagesize', 'cpu_largepagesize',
+ 'numa_numnodes', 'numa_factor', 'numa_interleaved',
+ ):
+ cpu[x] = json[x]
+
+ cpu['os'] = report.os()
+
+ def fmt_size(s):
+ if s % (1024*1024) == 0:
+ return "%d MB" % (s / (1024*1024))
+ if s % 1024 == 0:
+ return "%d kB" % (s / 1024)
+ return "%d B" % s
+
+ def fmt_assoc(w):
+ if w == 255:
+ return 'fully-assoc'
+ else:
+ return '%d-way' % w
+
+ def fmt_cache(c):
+ types = ('?', 'D', 'I ', 'U')
+ return "L%d %s: %s (%s, shared %dx, %dB line)" % (
+ c['level'], types[c['type']], fmt_size(c['totalsize']),
+ fmt_assoc(c['associativity']), c['sharedby'], c['linesize']
+ )
+
+ def fmt_tlb(c):
+ types = ('?', 'D', 'I ', 'U')
+ return "L%d %s: %d-entry (%s, %s page)" % (
+ c['level'], types[c['type']], c['entries'],
+ fmt_assoc(c['associativity']), fmt_size(c['pagesize'])
+ )
+
+ def fmt_caches(d, i, cb):
+ dcaches = d[:]
+ icaches = i[:]
+ caches = []
+ while len(dcaches) or len(icaches):
+ if len(dcaches) and len(icaches) and dcaches[0] == icaches[0]:
+ caches.append(cb(dcaches[0]))
+ dcaches.pop(0)
+ icaches.pop(0)
+ else:
+ if len(dcaches):
+ caches.append(cb(dcaches[0]))
+ dcaches.pop(0)
+ if len(icaches):
+ caches.append(cb(icaches[0]))
+ icaches.pop(0)
+ return tuple(caches)
+
+ cpu['caches'] = fmt_caches(json['x86_dcaches'], json['x86_icaches'], fmt_cache)
+ cpu['tlbs'] = fmt_caches(json['x86_dtlbs'], json['x86_itlbs'], fmt_tlb)
+
+ caps = set()
+ for (n,_,b) in x86.cap_bits:
+ if n.endswith('[2]'):
+ continue
+ if json['x86_caps[%d]' % (b / 32)] & (1 << (b % 32)):
+ caps.add(n)
+ cpu['caps'] = frozenset(caps)
+
+ all_users.add(report.user_id_hash)
+ cpus.setdefault(hashabledict(cpu), set()).add(report.user_id_hash)
+
+ return render_to_response('reports/cpu.html', {'cpus': cpus, 'x86_cap_descs': x86.cap_descs})
+
+def get_hwdetect_reports():
+ reports = UserReport_hwdetect.objects
+ reports = reports.filter(data_type = 'hwdetect', data_version__gte = 3)
+ return reports
+
+def report_opengl_json(request):
+ reports = get_hwdetect_reports()
+
+ devices = {}
+
+ for report in reports:
+ json = report.data_json()
+ if json is None:
+ continue
+
+ exts = report.gl_extensions()
+ limits = report.gl_limits()
+
+ devices.setdefault(hashabledict({'vendor': json['GL_VENDOR'], 'renderer': json['GL_RENDERER'], 'os': report.os()}), {}).setdefault((hashabledict(limits), exts), set()).add(json['gfx_drv_ver'])
+
+ distinct_devices = []
+ for (renderer, v) in devices.items():
+ for (caps, versions) in v.items():
+ distinct_devices.append((renderer, sorted(versions), caps))
+ distinct_devices.sort(key = lambda x: (x[0]['vendor'], x[0]['renderer'], x[0]['os'], x))
+
+ data = []
+ for r,vs,(limits,exts) in distinct_devices:
+ data.append({'device': r, 'versions': vs, 'limits': limits, 'extensions': sorted(exts)})
+ json = simplejson.dumps(data, indent=1, sort_keys=True)
+ return HttpResponse(json, content_type = 'text/plain')
+
+def device_identifier(json):
+ return json['GL_RENDERER']
+
+def report_opengl_index(request):
+ reports = get_hwdetect_reports()
+
+ all_limits = set()
+ all_exts = set()
+ all_devices = set()
+ ext_devices = {}
+
+ for report in reports:
+ json = report.data_json()
+ if json is None:
+ continue
+
+ device = device_identifier(json)
+ all_devices.add(device)
+
+ exts = report.gl_extensions()
+ all_exts |= exts
+ for ext in exts:
+ ext_devices.setdefault(ext, set()).add(device)
+
+ limits = report.gl_limits()
+ all_limits |= set(limits.keys())
+
+ all_limits = sorted(all_limits)
+ all_exts = sorted(all_exts)
+ all_devices = sorted(all_devices)
+
+ return render_to_response('reports/opengl_index.html', {
+ 'all_limits': all_limits,
+ 'all_exts': all_exts,
+ 'all_devices': all_devices,
+ 'ext_devices': ext_devices,
+ })
+
+def report_opengl_feature(request, feature):
+ reports = get_hwdetect_reports()
+
+ all_values = set()
+ values = {}
+ is_extension = False
+
+ for report in reports:
+ json = report.data_json()
+ if json is None:
+ continue
+
+ exts = report.gl_extensions()
+ limits = hashabledict(report.gl_limits())
+
+ val = None
+ if feature in exts:
+ val = True
+ is_extension = True
+ elif feature in limits:
+ val = limits[feature]
+ all_values.add(val)
+
+ values.setdefault(val, {}).setdefault(hashabledict({'vendor': json['GL_VENDOR'], 'renderer': json['GL_RENDERER'], 'os': report.os()}), set()).add(json['gfx_drv_ver'])
+
+ if values.keys() == [None]:
+ raise Http404
+
+ if is_extension:
+ values = {
+ 'true': values.get(True, {}),
+ 'false': values.get(None, {}),
+ }
+
+ return render_to_response('reports/opengl_feature.html', {
+ 'feature': feature,
+ 'all_values': all_values,
+ 'values': values,
+ 'is_extension': is_extension,
+ })
+
+def report_opengl_devices(request, selected):
+ reports = get_hwdetect_reports()
+
+ all_limits = set()
+ all_exts = set()
+ all_devices = set()
+ devices = {}
+
+ for report in reports:
+ json = report.data_json()
+ if json is None:
+ continue
+
+ device = device_identifier(json)
+ all_devices.add(device)
+ if device not in selected:
+ continue
+
+ exts = report.gl_extensions()
+ all_exts |= exts
+
+ limits = report.gl_limits()
+ all_limits |= set(limits.keys())
+
+ devices.setdefault(hashabledict({'vendor': json['GL_VENDOR'], 'renderer': json['GL_RENDERER'], 'os': report.os()}), {}).setdefault((hashabledict(limits), exts), set()).add(json['gfx_drv_ver'])
+
+ if len(selected) == 1 and len(devices) == 0:
+ raise Http404
+
+ all_limits = sorted(all_limits)
+ all_exts = sorted(all_exts)
+
+ distinct_devices = []
+ for (renderer, v) in devices.items():
+ for (caps, versions) in v.items():
+ distinct_devices.append((renderer, sorted(versions), caps))
+ distinct_devices.sort(key = lambda x: (x[0]['vendor'], x[0]['renderer'], x[0]['os'], x))
+
+ return render_to_response('reports/opengl_device.html', {
+ 'selected': selected,
+ 'all_limits': all_limits,
+ 'all_exts': all_exts,
+ 'all_devices': all_devices,
+ 'devices': distinct_devices,
+ })
+
+def report_opengl_device(request, device):
+ return report_opengl_devices(request, [device])
+
+def report_opengl_device_compare(request):
+ return report_opengl_devices(request, request.GET.getlist('d'))
diff --git a/source/tools/webservices/userreport/views_private.py b/source/tools/webservices/userreport/views_private.py
new file mode 100644
index 0000000000..b96708c91c
--- /dev/null
+++ b/source/tools/webservices/userreport/views_private.py
@@ -0,0 +1,45 @@
+from userreport.models import UserReport
+
+from django.http import HttpResponseForbidden
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.core.paginator import Paginator, InvalidPage, EmptyPage
+
+def render_reports(request, reports, template, args):
+ paginator = Paginator(reports, args.get('pagesize', 100))
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+ try:
+ report_page = paginator.page(page)
+ except (EmptyPage, InvalidPage):
+ report_page = paginator.page(paginator.num_pages)
+
+ args['report_page'] = report_page
+ return render_to_response(template, args)
+
+
+def report_user(request, user):
+ reports = UserReport.objects.order_by('-upload_date')
+ reports = reports.filter(user_id_hash = user)
+
+ return render_reports(request, reports, 'reports/user.html', {'user': user})
+
+def report_messages(request):
+ reports = UserReport.objects.order_by('-upload_date')
+ reports = reports.filter(data_type = 'message', data_version__gte = 1)
+
+ return render_reports(request, reports, 'reports/message.html', {})
+
+def report_profile(request):
+ reports = UserReport.objects.order_by('-upload_date')
+ reports = reports.filter(data_type = 'profile', data_version__gte = 1)
+
+ return render_reports(request, reports, 'reports/profile.html', {'pagesize': 20})
+
+def report_hwdetect(request):
+ reports = UserReport.objects.order_by('-upload_date')
+ reports = reports.filter(data_type = 'hwdetect', data_version__gte = 1)
+
+ return render_reports(request, reports, 'reports/hwdetect.html', {})
diff --git a/source/tools/webservices/userreport/x86.py b/source/tools/webservices/userreport/x86.py
new file mode 100644
index 0000000000..08839e67ea
--- /dev/null
+++ b/source/tools/webservices/userreport/x86.py
@@ -0,0 +1,156 @@
+# CPUID feature bits, from LSB to MSB:
+# (Names and descriptions gathered from various Intel and AMD sources)
+
+cap_raw = (
+# EAX=01H ECX:
+"""SSE3
+PCLMULQDQ
+DTES64: 64-bit debug store
+MONITOR: MONITOR/MWAIT
+DS-CPL: CPL qualified debug store
+VMX: virtual machine extensions
+SMX: safer mode extensions
+EST: enhanced SpeedStep
+TM2: thermal monitor 2
+SSSE3
+CNXT-ID: L1 context ID
+?(ecx11)
+FMA: fused multiply add
+CMPXCHG16B
+xTPR: xTPR update control
+PDCM: perfmon and debug capability
+?(ecx16)
+PCID: process context identifiers
+DCA: direct cache access
+SSE4_1
+SSE4_2
+x2APIC: extended xAPIC support
+MOVBE
+POPCNT
+TSC-DEADLINE
+AES
+XSAVE: XSAVE instructions supported
+OSXSAVE: XSAVE instructions enabled
+AVX
+F16C: half-precision convert
+?(ecx30)
+RAZ: used by hypervisor to indicate guest status
+""" +
+
+# EAX=01H EDX:
+"""FPU
+VME: virtual 8086 mode enhancements
+DE: debugging extension
+PSE: page size extension
+TSC: time stamp counter
+MSR: model specific registers
+PAE: physical address extension
+MCE: machine-check exception
+CMPXCHG8
+APIC
+?(edx10)
+SEP: fast system call
+MTRR: memory type range registers
+PGE: page global enable
+MCA: machine-check architecture
+CMOV
+PAT: page attribute table
+PSE-36: 36-bit page size extension
+PSN: processor serial number
+CLFSH: CLFLUSH
+?(edx20)
+DS: debug store
+ACPI
+MMX
+FXSR: FXSAVE and FXSTOR
+SSE
+SSE2
+SS: self-snoop
+HTT: hyper-threading
+TM: thermal monitor
+?(edx30)
+PBE: pending break enable
+""" +
+
+# EAX=80000001H ECX:
+"""LAHF: LAHF/SAHF instructions
+CMP: core multi-processing legacy mode
+SVM: secure virtual machine
+ExtApic
+AltMovCr8
+ABM: LZCNT instruction
+SSE4A
+MisAlignSse
+3DNowPrefetch
+OSVW: OS visible workaround
+IBS: instruction based sampling
+XOP: extended operation support
+SKINIT
+WDT: watchdog timer support
+?(ext:ecx14)
+LWP: lightweight profiling support
+FMA4: 4-operand FMA
+?(ext:ecx17)
+?(ext:ecx18)
+NodeId
+?(ext:ecx20)
+TBM: trailing bit manipulation extensions
+TopologyExtensions
+?(ext:ecx23)
+?(ext:ecx24)
+?(ext:ecx25)
+?(ext:ecx26)
+?(ext:ecx27)
+?(ext:ecx28)
+?(ext:ecx29)
+?(ext:ecx30)
+?(ext:ecx31)
+""" +
+
+# EAX=80000001H ECX:
+"""FPU[2]
+VME[2]
+DE[2]
+PSE[2]
+TSC[2]
+MSR[2]
+PAE[2]
+MCE[2]
+CMPXCHG8[2]
+APIC[2]
+?(ext:edx10)
+SYSCALL: SYSCALL/SYSRET instructions
+MTRR[2]
+PGE[2]
+MCA[2]
+CMOV[2]
+PAT[2]
+PSE36[2]
+?(ext:edx18)
+MP: MP-capable
+NX: no execute bit
+?(ext:edx21)
+MmxExt
+MMX[2]
+FXSR[2]
+FFXSR
+1GB: 1GB pages
+RDTSCP
+?(ext:edx28)
+x86-64
+3DNowExt
+3DNow
+"""
+)
+
+cap_bits = []
+cap_descs = {}
+idx = 0
+for c in cap_raw.strip().split('\n'):
+ s = c.split(':')
+ if len(s) == 1:
+ cap_bits.append((s[0], None, idx))
+ else:
+ cap_bits.append((s[0], s[1], idx))
+ cap_descs[s[0]] = s[1]
+ idx += 1
|