From 91a446f0051f01020c47685d38b3de1e0e400aae Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Sun, 26 Dec 2010 18:06:42 +0100 Subject: [PATCH] Big update of the fireinfo service. Very hard to break down and not neccessary. --- www/static/css/style.css | 54 +++ www/static/images/icons/bus-pci.png | Bin 0 -> 669 bytes www/static/images/icons/bus-usb.png | Bin 0 -> 823 bytes .../modules/stasy-table-devices.html | 30 ++ www/templates/modules/stasy-table.html | 8 +- www/templates/stasy-index.html | 23 +- www/templates/stasy-model-detail.html | 9 + www/templates/stasy-profile-detail.html | 209 ++++++++++++ www/templates/stasy-profile.html | 203 +----------- www/templates/stasy-stats-cpu-flags.html | 25 ++ www/templates/stasy-stats-cpus.html | 20 +- www/templates/stasy-stats-geo.html | 19 ++ www/templates/stasy-stats-memory.html | 12 + www/templates/stasy-stats-oses.html | 18 + www/templates/stasy-stats-virtual.html | 20 +- www/templates/stasy-stats.html | 22 +- www/templates/stasy-vendor-detail.html | 11 + www/webapp/__init__.py | 18 +- www/webapp/backend/stasy.py | 312 +++++++++++++++++- www/webapp/handlers_stasy.py | 120 ++++++- www/webapp/ui_modules.py | 36 +- 21 files changed, 938 insertions(+), 231 deletions(-) create mode 100644 www/static/images/icons/bus-pci.png create mode 100644 www/static/images/icons/bus-usb.png create mode 100644 www/templates/modules/stasy-table-devices.html create mode 100644 www/templates/stasy-model-detail.html create mode 100644 www/templates/stasy-profile-detail.html create mode 100644 www/templates/stasy-stats-cpu-flags.html create mode 100644 www/templates/stasy-stats-geo.html create mode 100644 www/templates/stasy-stats-memory.html create mode 100644 www/templates/stasy-stats-oses.html create mode 100644 www/templates/stasy-vendor-detail.html diff --git a/www/static/css/style.css b/www/static/css/style.css index 08d57cd..c0682f9 100644 --- a/www/static/css/style.css +++ b/www/static/css/style.css @@ -916,3 +916,57 @@ table.blocks td.block1,td.block3 { table.blocks td.block2 { background-color: #f5f5f5; } + +table.fireinfo { + width: 45em; + margin-left: 1.5em; +} + +table.fireinfo td.key { + width: 12.5em; +} + +table.fireinfo td.value { + width: 32.5em; +} + +table.cpufeatures tr { + line-height: 1.2em; +} + +table.cpufeatures td { + padding-left: 0.1em; + padding-right: 0.1em; +} + +table.cpufeatures td.enabled { + border: 1px solid #88ff88; + background-color: #aaffaa; +} + +table.cpufeatures td.disabled { + border: 1px solid #ff8888; + background-color: #ff9999; +} + +table.stats { + width: 45em; + margin-left: 1.5em; +} + +table.stats td.key { + width: 12.5em; +} + +table.stats td.value { + width: 2.5em; + text-align: right; +} + +table.stats td.bar { + width: 30em; +} + +table.stats td.bar p { + background-color: #880400; +} diff --git a/www/static/images/icons/bus-pci.png b/www/static/images/icons/bus-pci.png new file mode 100644 index 0000000000000000000000000000000000000000..ee746a7d2effdbad3fb3a82112f25c1e0a2a2fbe GIT binary patch literal 669 zc-jG-0%HA%P)U0~mKAwqH6U^2MlXoeHwv}&w%pq{rjDbLSZZQoqG$mPg{;iZ ztWsFO}ivC_wtkteiE-UQ#{iP$eIQuT1=+!rbe zNKLU8jVL0Y0UBC(_Vl?oJTla7HXI;|>q1xN03j35-IK&~Ak*8*p@XM5+}77%V0dH* zC+sYmY9+;l!nm%>iH=r`v7GEo;&?8dnG~upo%=`-1a*OEBo>)^Ig_*~S}Ipawshb{ zAR2|%{ar+(kZd_lQxtqZz=TWyrVO}Somdg_b5rvz8;Qvm)%a?%5d$kgWVNvB0_r%9 zU+sJszwy%Gz_>AzRn^?<*}0+WE2-{k_V@QUZ^i!rT~*n@sn1qt00000NkvXXu0mjf DfW$B! literal 0 Hc-jL100001 diff --git a/www/static/images/icons/bus-usb.png b/www/static/images/icons/bus-usb.png new file mode 100644 index 0000000000000000000000000000000000000000..d07c454e294b301deb2be414b25ada02d09e403b GIT binary patch literal 823 zc-jFs1IYY|P)HN6d`(-}imGf`CMK!`G5u5D}bp zq-g_H#i>@*b%ikoW6UsdH7_ZuethlW3g=JmXJ0$=JhWTR| zZ@<(OwQyv&_(+Ihi$?-srNN%@ zO++L&XU?8J2WG*0pKY7rFA$O37%rF? + {% for group, devices in groups %} + + +  
+ {{ _(group) }} + + + {% for device in devices %} + + +   + {{ device.subsystem.upper() }} + + + + {{ device.vendor_string }} + + - + + {{ device.model_string or "N/A (%s)" % device.model }} + +
+ {{ _("Module") }}: {{ device.driver or "N/A" }} + + + {% end %} + {% end %} + diff --git a/www/templates/modules/stasy-table.html b/www/templates/modules/stasy-table.html index 267179a..0c1353e 100644 --- a/www/templates/modules/stasy-table.html +++ b/www/templates/modules/stasy-table.html @@ -1,8 +1,10 @@ - +
{% for k, v in items %} - - + + + {% end %}
{{ k }}{{ "%.2f" % v }}%{{ k }}

 

{{ "%.2f" % v }}%
+
diff --git a/www/templates/stasy-index.html b/www/templates/stasy-index.html index 491dbed..0f84796 100644 --- a/www/templates/stasy-index.html +++ b/www/templates/stasy-index.html @@ -5,8 +5,25 @@ {% block content %}

{{ _("Statistical evaluation service") }}

- {% for p in profiles %} - {{ p }} - {% end %} +

+ Please enter your profile ID and have a look of the hardware components + that are installed on your system: +

+ +
+ {{ xsrf_form_html() }} +

+ + +

+
+
+ +

XXX Just for now...

+

+ {% for p in profiles %} + {{ p }} + {% end %} +

{% end block %} diff --git a/www/templates/stasy-model-detail.html b/www/templates/stasy-model-detail.html new file mode 100644 index 0000000..643846a --- /dev/null +++ b/www/templates/stasy-model-detail.html @@ -0,0 +1,9 @@ +{% extends "stasy-base-1.html" %} + +{% block content %} +

{{ vendor_name }} - {{ model_name or model_id }}

+

+ This device is installed on approximately {{ "%.2f" % percentage }}% of + all systems in the database. +

+{% end block %} diff --git a/www/templates/stasy-profile-detail.html b/www/templates/stasy-profile-detail.html new file mode 100644 index 0000000..3fe5047 --- /dev/null +++ b/www/templates/stasy-profile-detail.html @@ -0,0 +1,209 @@ +{% extends "stasy-base-2.html" %} + +{% block title %}{{ _("Profile") }} {{ profile.public_id }}{% end block %} + +{% block content %} +

{{ _("Profile") }} {{ profile.public_id }}

+ + {{ profile.country_code }} + + + + + + + + + + +
+ {{ _("Vendor") }} + + {{ profile.vendor }} +
+ {{ _("Model") }} + + {{ profile.model }} +
+ +

{{ _("Operating system") }}

+ + + + + + + + + + + + + +
+ {{ _("Version") }} + + {{ profile.release }} +
+ {{ _("Architecture") }} + + {{ profile.cpu.arch }} +
+ {{ _("Kernel version") }} + + {{ profile.kernel }} +
+ + {% if profile.hypervisor %} +

{{ _("Hypervisor") }}

+

+ {{ _("This machine is running in a virtual environment.") }} +

+ + + + + + + + + + +
+ {{ _("Vendor") }} + + {{ profile.hypervisor.vendor }} +
+ {{ _("Type") }} + + {{ profile.hypervisor.type }} +
+ {% end %} + +

{{ _("Hardware") }}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ _("CPU") }} +
+ {{ _("Vendor") }} + + {{ profile.cpu.vendor }} +
+ {{ _("Model") }} + + {{ profile.cpu.model_string or _("Not available") }} +
+ {{ _("Core count") }} + + {{ profile.cpu.count }} +
+ {{ _("Speed") }} + + {{ profile.cpu.speed }} MHz (Bogomips: {{ profile.cpu.bogomips }}) +
+ {{ _("Supported features") }} + + + + {% if profile.cpu.capable_64bit %} + +
+ {% else %} + + {% end %} + {{ _("64 bit") }} + + + {% if profile.cpu.capable_pae %} + + {% else %} + + {% end %} + {{ _("PAE") }} + + + {% if profile.cpu.capable_virt %} + + {% else %} + + {% end %} + {{ _("VT-x/AMD-V") }} + +
+
+ {{ _("Memory") }} +
+ {{ _("Size") }} + + {{ format_size(profile.memory) }} +
+ {{ _("Disk") }} +
+ {{ _("Size") }} + + {{ format_size(profile.root_size) }} +
+ {{ modules.StasyDeviceTable(profile.devices) }} +
+
+ +

{{ _("Signature images") }}

+ + {% for i in range(1) %} + + + + {% end %} +
+ + + {{ _( + +
+ + +{% end block %} diff --git a/www/templates/stasy-profile.html b/www/templates/stasy-profile.html index a3b4133..d1c8f5a 100644 --- a/www/templates/stasy-profile.html +++ b/www/templates/stasy-profile.html @@ -1,198 +1,19 @@ {% extends "stasy-base-1.html" %} -{% block title %}{{ _("Profile") }} {{ profile.public_id }}{% end block %} +{% block title %}{{ _("Profile lookup") }}{% end block %} {% block content %} -

{{ _("Profile") }} {{ profile.public_id }}

+

{{ _("Profile lookup") }}

- - - - - - - - - - - - - - - - -
- {{ _("Operating system") }} -
- {{ _("Version") }} - - {{ profile.release }} -
- {{ _("Architecture") }} - - {{ profile.cpu.arch }} -
- {{ _("Kernel version") }} - - {{ profile.kernel }} -
- - {% if profile.hypervisor %} -

{{ _("Hypervisor") }}

- - - - - - - - - - - - - - -
- {{ _("Vendor") }} - - {{ profile.hypervisor.vendor }} -
- {{ _("Model") }} - - {{ profile.hypervisor.model or _("Unknown") }} -
- {{ _("Type") }} - - {{ profile.hypervisor.type }} -
- {% end %} - -

{{ _("Hardware") }}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {{ _("CPU") }} -
- {{ _("Vendor") }} - - {{ profile.cpu.vendor }} -
- {{ _("Model") }} - - {{ profile.cpu.model_string or _("Not available") }} -
- {{ _("Supports x86_64") }} - - {% if profile.cpu.capable_64bit %} - YES - {% else %} - NO - {% end %} -
- {{ _("Supports PAE") }} - - {% if profile.cpu.capable_pae %} - YES - {% else %} - NO - {% end %} -
- {{ _("Supports VT-x/AMD-V") }} - - {% if profile.cpu.capable_virt %} - YES - {% else %} - NO - {% end %} -
- {{ _("Memory") }} -
- {{ _("Size") }} - - {{ int(profile.memory) / 1024 }} {{ _("MegaBytes") }} -
- {{ _("Disk") }} -
- {{ _("Size") }} - - {{ profile.root_size / 1024 }} {{ _("MegaBytes") }} -
- {{ _("Devices") }} -
- - {% for device in profile.devices %} - - - - - - - {% end %} -
- {{ device.subsystem.upper() }} - - {{ device.vendor_string }} - - {{ device.model_string }} - - {{ device.driver or " " }} -
-
- -

+ Please enter your profile ID and have a look of the hardware components + that are installed on your system:

+ +
+

+ + +

+
{% end block %} diff --git a/www/templates/stasy-stats-cpu-flags.html b/www/templates/stasy-stats-cpu-flags.html new file mode 100644 index 0000000..41771ac --- /dev/null +++ b/www/templates/stasy-stats-cpu-flags.html @@ -0,0 +1,25 @@ +{% extends "stasy-base-2.html" %} + +{% block title %}{% end block %} + +{% block content %} +

{{ _("CPU flags") }}

+

+ On this page we will examine which CPUs provide a specific feature. + These charts will help the IPFire developers to make decisions about + the operating system configuration. +

+ +

{{ _("CPUs that support long mode") }}

+

+ For future planning it is important to know if there is enough hardware + support for a 64bit version of IPFire. +

+ {{ modules.StasyTable(cpus_lm, sortby="percentage") }} + +

{{ _("CPUs with PAE") }}

+

+ This chart shows us which CPUs have got the PAE flag. +

+ {{ modules.StasyTable(cpus_pae, sortby="percentage") }} +{% end block %} diff --git a/www/templates/stasy-stats-cpus.html b/www/templates/stasy-stats-cpus.html index da8a795..435eec0 100644 --- a/www/templates/stasy-stats-cpus.html +++ b/www/templates/stasy-stats-cpus.html @@ -1,7 +1,23 @@ -{% extends "stasy-base-1.html" %} +{% extends "stasy-base-2.html" %} {% block title %}{% end block %} {% block content %} - {{ modules.StasyTable(cpu_vendors) }} +

{{ _("CPU vendors") }}

+

+ This chart shows us which vendors is the most favorite for the CPUs + used in IPFire. +

+ {{ modules.StasyTable(cpu_vendors, sortby="percentage") }} + +

{{ _("Speed") }}

+

+ The average speed of all systems in the database is: + {{ "%.2f" % average_speed }} MHz. +

+ {{ modules.StasyTable(cpu_speeds) }} + + {% end block %} diff --git a/www/templates/stasy-stats-geo.html b/www/templates/stasy-stats-geo.html new file mode 100644 index 0000000..ca9d8aa --- /dev/null +++ b/www/templates/stasy-stats-geo.html @@ -0,0 +1,19 @@ +{% extends "stasy-base-2.html" %} + +{% block title %}{% end block %} + +{% block content %} +

{{ _("Language selection") }}

+

+ This will give a short overview about what languages are configured + on the IPFire webinterface. +

+ {{ modules.StasyTable(languages, sortby="percentage") }} + + +

{{ _("Geo locations") }}

+

+ This chart shows us in which country IPFire is running. +

+ {{ modules.StasyTable(geo_locations, sortby="percentage") }} +{% end block %} diff --git a/www/templates/stasy-stats-memory.html b/www/templates/stasy-stats-memory.html new file mode 100644 index 0000000..4c46b68 --- /dev/null +++ b/www/templates/stasy-stats-memory.html @@ -0,0 +1,12 @@ +{% extends "stasy-base-2.html" %} + +{% block title %}{% end block %} + +{% block content %} +

{{ _("Memory") }}

+

+ The average amount of memory of all systems in the database is: + {{ "%.2f" % average_memory }} MB. +

+ {{ modules.StasyTable(memory) }} +{% end block %} diff --git a/www/templates/stasy-stats-oses.html b/www/templates/stasy-stats-oses.html new file mode 100644 index 0000000..49c5c82 --- /dev/null +++ b/www/templates/stasy-stats-oses.html @@ -0,0 +1,18 @@ +{% extends "stasy-base-2.html" %} + +{% block title %}{% end block %} + +{% block content %} +

{{ _("Releases") }}

+ {{ modules.StasyTable(releases, sortby="percentage") }} + +

{{ _("Architectures") }}

+ {{ modules.StasyTable(arches, sortby="percentage") }} + +

{{ _("Kernels") }}

+

+ This chart gives an overview about what kernel the machines are + runnning. +

+ {{ modules.StasyTable(kernels, sortby="percentage") }} +{% end block %} diff --git a/www/templates/stasy-stats-virtual.html b/www/templates/stasy-stats-virtual.html index c4442dc..c3f64a2 100644 --- a/www/templates/stasy-stats-virtual.html +++ b/www/templates/stasy-stats-virtual.html @@ -1,9 +1,21 @@ -{% extends "stasy-base-1.html" %} +{% extends "stasy-base-2.html" %} {% block title %}{% end block %} {% block content %} - {{ modules.StasyTable(is_virtual) }} - - {{ modules.StasyTable(hypervisor_vendors) }} +

{{ _("Virtualization support") }}

+

+ IPFire is running very well in a virtual environment. +

+

+ See this chart to get a clue about how many machines are running + virtually. +

+ {{ modules.StasyTable(is_virtual, sortby="percentage") }} + +

{{ _("Hypervisors") }}

+

+ This is a list of all hypervisor vendors that IPFire is running on. +

+ {{ modules.StasyTable(hypervisor_vendors, sortby="percentage") }} {% end block %} diff --git a/www/templates/stasy-stats.html b/www/templates/stasy-stats.html index 8d0580e..14b1b82 100644 --- a/www/templates/stasy-stats.html +++ b/www/templates/stasy-stats.html @@ -1,2 +1,22 @@ -{% extends "stasy-base-1.html" %} +{% extends "stasy-base-2.html" %} +{% block content %} +

{{ _("Statistical accounting") }}

+

+ At this place you can find several charts to find out in what + specific way the hardware that is used for IPFire is ... XXX. +

+ + + + + + + + + + + + +
{{ _("CPUs") }}{{ _("CPU flags") }}{{ _("Memory") }}
{{ _("IPFire versions") }}{{ _("Virtualization") }}{{ _("Geographical information") }}
+{% end block %} diff --git a/www/templates/stasy-vendor-detail.html b/www/templates/stasy-vendor-detail.html new file mode 100644 index 0000000..1bef184 --- /dev/null +++ b/www/templates/stasy-vendor-detail.html @@ -0,0 +1,11 @@ +{% extends "stasy-base-1.html" %} + +{% block content %} +

{{ vendor_name }}

+

+ This is a list of all devices this database knows about from + {{ vendor_name }}. +

+ + {{ modules.StasyDeviceTable(models) }} +{% end block %} diff --git a/www/webapp/__init__.py b/www/webapp/__init__.py index 40eac14..8b40a68 100644 --- a/www/webapp/__init__.py +++ b/www/webapp/__init__.py @@ -39,6 +39,7 @@ class Application(tornado.web.Application): "SidebarBanner" : SidebarBannerModule, "SidebarRelease" : SidebarReleaseModule, "StasyTable" : StasyTableModule, + "StasyDeviceTable" : StasyDeviceTableModule, "TrackerPeerList": TrackerPeerListModule, }, xsrf_cookies = True, @@ -116,11 +117,20 @@ class Application(tornado.web.Application): ] + static_handlers) # stasy.ipfire.org - self.add_handlers(r"stasy\.ipfire\.org", [ + self.add_handlers(r"(fireinfo|stasy)\.ipfire\.org", [ (r"/", StasyIndexHandler), - (r"/profile/([a-z0-9]{40})", StasyProfileHandler), - (r"/statistics/cpu", StasyStatsCPUHandler), - (r"/statistics/virtual", StasyStatsVirtualHandler), + (r"/profile/([a-z0-9]{40})", StasyProfileDetailHandler), + (r"/vendor/(pci|usb)/([0-9a-f]{4})", StasyStatsVendorDetail), + (r"/model/(pci|usb)/([0-9a-f]{4})/([0-9a-f]{4})", StasyStatsModelDetail), + + # Stats handlers + (r"/stats", StasyStatsHandler), + (r"/stats/cpus", StasyStatsCPUHandler), + (r"/stats/cpuflags", StasyStatsCPUFlagsHandler), + (r"/stats/geo", StasyStatsGeoHandler), + (r"/stats/memory", StasyStatsMemoryHandler), + (r"/stats/oses", StasyStatsOSesHandler), + (r"/stats/virtual", StasyStatsVirtualHandler), ] + static_handlers) # i-use.ipfire.org diff --git a/www/webapp/backend/stasy.py b/www/webapp/backend/stasy.py index 6f13a98..26a241b 100644 --- a/www/webapp/backend/stasy.py +++ b/www/webapp/backend/stasy.py @@ -1,5 +1,7 @@ #!/usr/bin/python +from __future__ import division + import hwdata import logging import pymongo @@ -9,6 +11,7 @@ from misc import Singleton DATABASE_HOST = ["irma.ipfire.org", "madeye.ipfire.org"] DATABASE_NAME = "stasy" +CPU_SPEED_CONSTRAINTS = (0, 500, 1000, 1500, 2000, 2500, 3000, 3500) MEMORY_CONSTRAINTS = (0, 64, 128, 256, 512, 1024, 2048, 4096, 8128, 16384) class ProfileDict(object): @@ -25,6 +28,14 @@ class ProfileCPU(ProfileDict): def vendor(self): return self._data.get("vendor") + @property + def speed(self): + return self._data.get("speed") + + @property + def bogomips(self): + return self._data.get("bogomips") + @property def model(self): return self._data.get("model") @@ -37,6 +48,10 @@ class ProfileCPU(ProfileDict): def flags(self): return self._data.get("flags") + @property + def count(self): + return self._data.get("count") + @property def capable_64bit(self): return "lm" in self.flags @@ -58,10 +73,6 @@ class ProfileHypervisor(ProfileDict): def vendor(self): return self._data.get("vendor") - @property - def model(self): - return self._data.get("model") - @property def type(self): return self._data.get("type") @@ -73,6 +84,57 @@ class ProfileDevice(ProfileDict): "usb" : hwdata.USB, } + classid2name = { + "pci" : { + "00" : "unclassified", + "01" : "Mass storage", + "02" : "Network", + "03" : "Display", + "04" : "Multimedia", + "05" : "Memory controller", + "06" : "Bridge", + "07" : "Communication", + "08" : "Generic system peripheral", + "09" : "Input device", + "0a" : "Docking station", + "0b" : "Processor", + "0c" : "Serial bus", + "0d" : "Wireless", + "0e" : "Intelligent controller", + "0f" : "Satellite communications controller", + "10" : "Encryption controller", + "11" : "Signal processing controller", + "ff" : "Unassigned class", + }, + + "usb" : { + "00" : "???", + "01" : "Multimedia", + "02" : "Communication", + "03" : "Input device", + "05" : "Physical ???", + "06" : "Image ???", + "07" : "Printer", + "08" : "Mass storage", + "09" : "Hub", + "0a" : "CDC-Data ???", + "0b" : "Smart card ???", + "0d" : "Content Security ???", + "0e" : "Display", + "0f" : "Personal healthcare ???", + "dc" : "Diagnostic Device", + "e0" : "Wireless", + "ef" : "Misc", + "fe" : "Application specific ???", + "ff" : "Vendor specific ???", + } + } + + def __cmp__(self, other): + return cmp(self.vendor, other.vendor) or \ + cmp(self.model, other.model) or \ + cmp(self.driver, other.driver) + @property def model(self): return self._data.get("model") @@ -101,13 +163,32 @@ class ProfileDevice(ProfileDict): def driver(self): return self._data.get("driver") + @property + def cls(self): + classid = self._data.get("deviceclass") + + if self.subsystem == "pci": + classid = classid[:-4] + if len(classid) == 1: + classid = "0%s" % classid + + elif self.subsystem == "usb" and classid: + classid = classid.split("/")[0] + classid = "%02x" % int(classid) + + try: + return self.classid2name[self.subsystem][classid] + except KeyError: + return "N/A" + class Profile(ProfileDict): - def __init__(self, public_id, updated, data): - ProfileDict.__init__(self, data) + def __init__(self, profile_blob): + ProfileDict.__init__(self, profile_blob.get("profile")) - self.public_id = public_id - self.updated = updated + self.public_id = profile_blob.get("public_id") + self.updated = profile_blob.get("updated") + self._geoip = profile_blob.get("geoip", None) def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.public_id) @@ -122,7 +203,7 @@ class Profile(ProfileDict): for d in self._data.get("devices"): d = ProfileDevice(d) - if d.driver in ("usb", "hub"): + if d.driver in ("pcieport", "usb", "hub"): continue devices.append(d) @@ -156,8 +237,22 @@ class Profile(ProfileDict): @property def root_size(self): - return self.system.get("root_size") + return self.system.get("root_size") or 0 + + @property + def vendor(self): + return self.system.get("vendor") + + @property + def model(self): + return self.system.get("model") + + @property + def country_code(self): + if self._geoip: + return self._geoip["country_code"].lower() + return "unknown" class Stasy(object): @@ -181,10 +276,13 @@ class Stasy(object): # # return c + def profile_exists(self, public_id): + return self._db.profiles.find({ "public_id" : public_id }).count() >= 1 + def get_profile(self, public_id): # XXX should only find one object in the end for p in self._db.profiles.find({ "public_id" : public_id }): - p = Profile(p.get("public_id"), p.get("updated"), p.get("profile")) + p = Profile(p) return p @@ -210,7 +308,7 @@ class Stasy(object): return self._db.profiles.distinct("profile.cpu.vendor") @property - def cpu_map(self): + def cpu_vendors_map(self): cpus = {} for vendor in self.cpu_vendors: @@ -222,7 +320,36 @@ class Stasy(object): return cpus @property - def memory_map(self): + def cpu_speed_average(self): + speed = 0 + + all = self._db.profiles.find() + + # XXX ugly. needs to be done by group() + for m in all: + if not m.has_key("profile"): + continue + speed += m.get("profile").get("cpu").get("speed") + + return (speed / all.count()) + + @property + def cpu_speed_map(self): + cpu_speeds = {} + + for i in range(len(CPU_SPEED_CONSTRAINTS) - 1): + min, max = CPU_SPEED_CONSTRAINTS[i:i+2] + + cpu_speeds[min, max] = \ + self._db.profiles.find( + { "profile.cpu.speed" : { + "$gte" : min, "$lt" : max + } + }).count() + + return cpu_speeds + + def get_memory_map(self): memory = {} for i in range(len(MEMORY_CONSTRAINTS) - 1): @@ -288,6 +415,17 @@ class Stasy(object): def languages(self): return self._db.profiles.distinct("profile.system.language") + def get_language_map(self): + languages = {} + + for language in self.languages: + languages[language] = \ + self._db.profiles.find({ + "profile.system.language" : language + }).count() + + return languages + @property def vendors(self): return self._db.profiles.distinct("profile.system.vendor") @@ -308,6 +446,141 @@ class Stasy(object): def models(self): return self._db.profiles.distinct("profile.system.model") + @property + def model_map(self): + models = {} + + for model in self.models: + models[model] = \ + self._db.profiles.find({ + "profile.system.model" : model + }).count() + + return models + + @property + def arches(self): + return self._db.profiles.distinct("profile.cpu.arch") + + @property + def arch_map(self): + arches = {} + + for arch in self.arches: + arches[arch] = \ + self._db.profiles.find({ + "profile.cpu.arch" : arch + }).count() + + return arches + + @property + def kernels(self): + return self._db.profiles.distinct("profile.system.kernel_release") + + @property + def kernel_map(self): + kernels = {} + + for kernel in self.kernels: + kernels[kernel] = \ + self._db.profiles.find({ + "profile.system.kernel_release" : kernel + }).count() + + return kernels + + @property + def releases(self): + return self._db.profiles.distinct("profile.system.release") + + @property + def release_map(self): + releases = {} + + for release in self.releases: + releases[release] = \ + self._db.profiles.find({ + "profile.system.release" : release + }).count() + + return releases + + def get_device_percentage(self, bus, vendor_id, model_id): + profiles_with_device = self._db.profiles.find({ + "profile.devices.subsystem" : bus, + "profile.devices.vendor" : vendor_id, + "profile.devices.model" : model_id, + }) + + # XXX must only be systems that have profile data + profiles_all = self._db.profiles.find() + + if not profiles_all.count(): + return 0 + + return profiles_with_device.count() / profiles_all.count() + + def get_cpu_flag_map(self, flag): + flags = {} + + flags[True] = \ + self._db.profiles.find({ + "profile.cpu.flags" : flag, + }).count() + + # XXX must only be systems that have profile data + profiles_all = self._db.profiles.find() + + flags[False] = profiles_all.count() - flags[True] + + return flags + + @property + def geo_locations(self): + return [code.lower() for code in self._db.profiles.distinct("geoip.country_code")] + + def get_geo_location_map(self): + geo_locations = {} + + count = 0 + for geo_location in self.geo_locations: + geo_locations[geo_location] = \ + self._db.profiles.find({ + "geoip.country_code" : geo_location + }).count() + + count += geo_locations[geo_location] + + # XXX must only be systems that have profile data + profiles_all = self._db.profiles.find() + + unknown_count = profiles_all.count() - count + if unknown_count: + geo_locations["unknown"] = unknown_count + + return geo_locations + + def get_models_by_vendor(self, subsystem, vendor_id): + devices = [] + + # XXX must only be systems that have profile data + profiles_all = self._db.profiles.find() + + for profile in profiles_all: + if not profile.has_key("profile"): + continue + + profile = Profile(profile) + + for device in profile.devices: + if not device.vendor == vendor_id: + continue + + if not device in devices: + devices.append(device) + + return devices if __name__ == "__main__": @@ -327,11 +600,20 @@ if __name__ == "__main__": print s.hypervisor_vendors print s.hypervisor_models print s.languages + print s.language_maps print s.vendors print s.vendor_map print s.models print s.cpus + print s.cpu_map + print s.arches + print s.arch_map + print s.kernels + print s.kernel_map + print s.releases + print s.release_map p = s.get_profile("0b5f4fe2162fdfbfa29b632610e317078fa70d34") - print p - print p.hypervisor + print p._data +# print p.hypervisor + diff --git a/www/webapp/handlers_stasy.py b/www/webapp/handlers_stasy.py index 7b8c506..6a66ee7 100644 --- a/www/webapp/handlers_stasy.py +++ b/www/webapp/handlers_stasy.py @@ -1,5 +1,8 @@ #!/usr/bin/python +from __future__ import division + +import hwdata import tornado.web import backend @@ -11,6 +14,30 @@ class StasyBaseHandler(BaseHandler): def stasy(self): return backend.Stasy() + def format_size(self, s): + units = ("K", "M", "G", "T") + unit = 0 + + while s >= 1024 and unit < len(units): + s /= 1024 + unit += 1 + + return "%.1f%s" % (s, units[unit]) + + def cut_string(self, s, limit=15): + if len(s) > limit: + s = s[:limit] + "..." + + return s + + def render(self, *args, **kwargs): + kwargs.update({ + "cut_string" : self.cut_string, + "format_size" : self.format_size, + }) + + return BaseHandler.render(self, *args, **kwargs) + class StasyIndexHandler(StasyBaseHandler): def get(self): @@ -18,20 +45,61 @@ class StasyIndexHandler(StasyBaseHandler): self.render("stasy-index.html", profiles=profiles) + def post(self): + profile_id = self.get_argument("profile_id", None) + if not profile_id: + raise tornado.web.HTTPError(400, "No profile ID was given.") + + if not self.stasy.profile_exists(profile_id): + raise tornado.web.HTTPError(404, "Profile does not exist.") -class StasyProfileHandler(StasyBaseHandler): + self.redirect("/profile/%s" % profile_id) + + +class StasyProfileDetailHandler(StasyBaseHandler): def get(self, profile_id): profile = self.stasy.get_profile(profile_id) if not profile: raise tornado.web.HTTPError(404, "Profile not found: %s" % profile_id) - self.render("stasy-profile.html", profile=profile) + self.render("stasy-profile-detail.html", profile=profile) + + +class StasyStatsHandler(StasyBaseHandler): + def get(self): + self.render("stasy-stats.html") class StasyStatsCPUHandler(StasyBaseHandler): def get(self): return self.render("stasy-stats-cpus.html", - cpu_vendors = self.stasy.cpu_map) + cpu_vendors=self.stasy.cpu_vendors_map, + average_speed=self.stasy.cpu_speed_average, + cpu_speeds=self.stasy.cpu_speed_map) + + +class StasyStatsCPUFlagsHandler(StasyBaseHandler): + def get(self): + kwargs = {} + + for flag in ("lm", "pae"): + kwargs["cpus_" + flag] = self.stasy.get_cpu_flag_map(flag) + + return self.render("stasy-stats-cpu-flags.html", **kwargs) + +class StasyStatsMemoryHandler(StasyBaseHandler): + def get(self): + return self.render("stasy-stats-memory.html", + average_memory=self.stasy.memory_average, + memory=self.stasy.get_memory_map()) + + +class StasyStatsOSesHandler(StasyBaseHandler): + def get(self): + return self.render("stasy-stats-oses.html", + arches=self.stasy.arch_map, + kernels=self.stasy.kernel_map, + releases=self.stasy.release_map) class StasyStatsVirtualHandler(StasyBaseHandler): @@ -39,3 +107,49 @@ class StasyStatsVirtualHandler(StasyBaseHandler): return self.render("stasy-stats-virtual.html", hypervisor_vendors = self.stasy.hypervisor_map, is_virtual = self.stasy.virtual_map) + +class StasyStatsGeoHandler(StasyBaseHandler): + def get(self): + return self.render("stasy-stats-geo.html", + languages = self.stasy.get_language_map(), + geo_locations = self.stasy.get_geo_location_map()) + + +class StasyStatsVendorDetail(StasyBaseHandler): + def get(self, bus, vendor_id): + # XXX some way ugly + bus2cls = { + "pci" : hwdata.PCI, + "usb" : hwdata.USB + } + cls = bus2cls[bus.lower()] + vendor_name = cls().get_vendor(vendor_id) + + # Get a list of all models we know from this vendor + models = self.stasy.get_models_by_vendor(bus, vendor_id) + + self.render("stasy-vendor-detail.html", + vendor_name=vendor_name, models=models) + + +class StasyStatsModelDetail(StasyBaseHandler): + def get(self, bus, vendor_id, model_id): + bus2cls = { + "pci" : hwdata.PCI, + "usb" : hwdata.USB + } + + cls = bus2cls[bus.lower()] + + vendor_name = cls().get_vendor(vendor_id) + model_name = cls().get_device(vendor_id, model_id) + + percentage = \ + self.stasy.get_device_percentage(bus, vendor_id, model_id) * 100 + + self.render("stasy-model-detail.html", + vendor_id=vendor_id, + vendor_name=vendor_name, + model_id=model_id, + model_name=model_name, + percentage=percentage) diff --git a/www/webapp/ui_modules.py b/www/webapp/ui_modules.py index 0ed5ccf..fab4a3a 100644 --- a/www/webapp/ui_modules.py +++ b/www/webapp/ui_modules.py @@ -1,6 +1,9 @@ #!/usr/bin/python +from __future__ import division + import logging +import operator import socket import textile import tornado.escape @@ -9,6 +12,7 @@ import tornado.web from tornado.database import Row import backend +import backend.stasy class UIModule(tornado.web.UIModule): @property @@ -125,18 +129,40 @@ class TrackerPeerListModule(UIModule): class StasyTableModule(UIModule): - def render(self, items): + def render(self, items, sortby="key", reverse=False): hundred_percent = 0 for v in items.values(): hundred_percent += v + keys = [] + if sortby == "key": + keys = sorted(items.keys(), reverse=reverse) + elif sortby == "percentage": + keys = [k for k,v in sorted(items.items(), key=operator.itemgetter(1))] + if not reverse: + keys = reversed(keys) + else: + raise Exception, "Unknown sortby parameter was provided" + if hundred_percent: _items = [] - for k in sorted(items.keys()): - v = float(items[k] * 100) / hundred_percent + for k in keys: + v = items[k] * 100 / hundred_percent _items.append((k, v)) items = _items - print items - return self.render_string("modules/stasy-table.html", items=items) + + +class StasyDeviceTableModule(UIModule): + def render(self, devices): + groups = {} + + for device in devices: + if not groups.has_key(device.cls): + groups[device.cls] = [] + + groups[device.cls].append(device) + + return self.render_string("modules/stasy-table-devices.html", + groups=groups.items()) -- 2.39.2