]>
Commit | Line | Data |
---|---|---|
66862195 MT |
1 | #!/usr/bin/python |
2 | ||
66862195 MT |
3 | import datetime |
4 | import hwdata | |
5 | import logging | |
6 | import re | |
a95c2f97 | 7 | import json |
66862195 MT |
8 | import tornado.web |
9 | ||
a95c2f97 | 10 | from .. import fireinfo |
66862195 | 11 | |
124a8404 | 12 | from . import base |
96c9bb79 | 13 | from . import ui_modules |
66862195 | 14 | |
96c9bb79 | 15 | class BaseHandler(base.BaseHandler): |
66862195 MT |
16 | @property |
17 | def when(self): | |
18 | return self.get_argument_date("when", None) | |
19 | ||
20 | ||
21 | MIN_PROFILE_VERSION = 0 | |
22 | MAX_PROFILE_VERSION = 0 | |
23 | ||
24 | class Profile(dict): | |
25 | def __getattr__(self, key): | |
26 | try: | |
27 | return self[key] | |
28 | except KeyError: | |
11347e46 | 29 | raise AttributeError(key) |
66862195 MT |
30 | |
31 | def __setattr__(self, key, val): | |
32 | self[key] = val | |
33 | ||
34 | ||
96c9bb79 | 35 | class ProfileSendHandler(BaseHandler): |
66862195 MT |
36 | def check_xsrf_cookie(self): |
37 | # This cookie is not required here. | |
38 | pass | |
39 | ||
40 | def prepare(self): | |
41 | # Create an empty profile. | |
42 | self.profile = Profile() | |
43 | ||
44 | def __check_attributes(self, profile): | |
45 | """ | |
46 | Check for attributes that must be provided, | |
47 | """ | |
48 | attributes = ( | |
49 | "private_id", | |
50 | "profile_version", | |
51 | "public_id", | |
52 | ) | |
53 | for attr in attributes: | |
11347e46 | 54 | if attr not in profile: |
66862195 MT |
55 | raise tornado.web.HTTPError(400, "Profile lacks '%s' attribute: %s" % (attr, profile)) |
56 | ||
57 | def __check_valid_ids(self, profile): | |
58 | """ | |
59 | Check if IDs contain valid data. | |
60 | """ | |
61 | for id in ("public_id", "private_id"): | |
62 | if re.match(r"^([a-f0-9]{40})$", "%s" % profile[id]) is None: | |
63 | raise tornado.web.HTTPError(400, "ID '%s' has wrong format: %s" % (id, profile)) | |
64 | ||
65 | def __check_equal_ids(self, profile): | |
66 | """ | |
67 | Check if public_id and private_id are equal. | |
68 | """ | |
69 | if profile.public_id == profile.private_id: | |
70 | raise tornado.web.HTTPError(400, "Public and private IDs are equal: %s" % profile) | |
71 | ||
72 | def __check_matching_ids(self, profile): | |
73 | """ | |
74 | Check if a profile with the given public_id is already in the | |
75 | database. If so we need to check if the private_id matches. | |
76 | """ | |
77 | p = self.profiles.find_one({ "public_id" : profile["public_id"]}) | |
78 | if not p: | |
79 | return | |
80 | ||
81 | p = Profile(p) | |
82 | if p.private_id != profile.private_id: | |
83 | raise tornado.web.HTTPError(400, "Mismatch of private_id: %s" % profile) | |
84 | ||
85 | def __check_profile_version(self, profile): | |
86 | """ | |
87 | Check if this version of the server software does support the | |
88 | received profile. | |
89 | """ | |
90 | version = profile.profile_version | |
91 | ||
92 | if version < MIN_PROFILE_VERSION or version > MAX_PROFILE_VERSION: | |
93 | raise tornado.web.HTTPError(400, | |
94 | "Profile version is not supported: %s" % version) | |
95 | ||
96 | def check_profile_blob(self, profile): | |
97 | """ | |
98 | This method checks if the blob is sane. | |
99 | """ | |
100 | checks = ( | |
101 | self.__check_attributes, | |
102 | self.__check_valid_ids, | |
103 | self.__check_equal_ids, | |
104 | self.__check_profile_version, | |
105 | # These checks require at least one database query and should be done | |
106 | # at last. | |
107 | self.__check_matching_ids, | |
108 | ) | |
109 | ||
110 | for check in checks: | |
111 | check(profile) | |
112 | ||
113 | # If we got here, everything is okay and we can go on... | |
114 | ||
115 | def get_profile_blob(self): | |
116 | profile = self.get_argument("profile", None) | |
117 | ||
118 | # Send "400 bad request" if no profile was provided | |
119 | if not profile: | |
120 | raise tornado.web.HTTPError(400, "No profile received") | |
121 | ||
122 | # Try to decode the profile. | |
123 | try: | |
a95c2f97 | 124 | return json.loads(profile) |
11347e46 | 125 | except json.decoder.JSONDecodeError as e: |
66862195 MT |
126 | raise tornado.web.HTTPError(400, "Profile could not be decoded: %s" % e) |
127 | ||
128 | # The GET method is only allowed in debugging mode. | |
129 | def get(self, public_id): | |
130 | if not self.application.settings["debug"]: | |
fe947d95 | 131 | raise tornado.web.HTTPError(405) |
66862195 MT |
132 | |
133 | return self.post(public_id) | |
134 | ||
135 | def post(self, public_id): | |
136 | profile_blob = self.get_profile_blob() | |
137 | #self.check_profile_blob(profile_blob) | |
138 | ||
139 | # Get location | |
140 | location = self.get_remote_location() | |
141 | if location: | |
142 | location = location.country | |
143 | ||
144 | # Handle the profile. | |
a69e87a1 MT |
145 | with self.db.transaction(): |
146 | try: | |
147 | self.fireinfo.handle_profile(public_id, profile_blob, location=location) | |
66862195 | 148 | |
a69e87a1 MT |
149 | except fireinfo.ProfileParserError: |
150 | raise tornado.web.HTTPError(400) | |
66862195 MT |
151 | |
152 | self.finish("Your profile was successfully saved to the database.") | |
153 | ||
154 | ||
96c9bb79 | 155 | class IndexHandler(BaseHandler): |
66862195 MT |
156 | def get(self): |
157 | self.render("fireinfo/index.html") | |
158 | ||
66862195 | 159 | |
b84b407f | 160 | class ProfileHandler(BaseHandler): |
66862195 MT |
161 | def get(self, profile_id): |
162 | profile = self.fireinfo.get_profile(profile_id, when=self.when) | |
163 | ||
164 | if not profile or not profile.is_showable(): | |
b84b407f | 165 | raise tornado.web.HTTPError(404) |
66862195 | 166 | |
b84b407f | 167 | self.render("fireinfo/profile.html", profile=profile) |
66862195 MT |
168 | |
169 | ||
96c9bb79 | 170 | class RandomProfileHandler(BaseHandler): |
66862195 MT |
171 | def get(self): |
172 | profile_id = self.fireinfo.get_random_profile(when=self.when) | |
173 | if profile_id is None: | |
174 | raise tornado.web.HTTPError(404) | |
175 | ||
176 | self.redirect("/profile/%s" % profile_id) | |
177 | ||
178 | ||
96c9bb79 | 179 | class DeviceDriverDetail(BaseHandler): |
66862195 MT |
180 | def get(self, driver): |
181 | self.render("fireinfo/driver.html", driver=driver, | |
182 | driver_map=self.fireinfo.get_driver_map(driver, when=self.when)) | |
183 | ||
184 | ||
96c9bb79 | 185 | class DeviceVendorsHandler(BaseHandler): |
66862195 MT |
186 | def get(self): |
187 | vendors = self.fireinfo.get_vendor_list(when=self.when) | |
188 | ||
189 | self.render("fireinfo/vendors.html", vendors=vendors) | |
190 | ||
191 | ||
96c9bb79 | 192 | class StatsHandler(BaseHandler): |
66862195 MT |
193 | def get(self): |
194 | self.render("fireinfo/stats.html") | |
195 | ||
196 | ||
96c9bb79 | 197 | class StatsProcessorsHandler(BaseHandler): |
66862195 MT |
198 | def get(self): |
199 | avg, stddev, min, max = self.fireinfo.get_cpu_clock_speeds(when=self.when) | |
200 | arch_map = self.fireinfo.get_arch_map(when=self.when) | |
201 | ||
202 | data = { | |
203 | "arch_map" : arch_map, | |
204 | "cpu_vendors" : self.fireinfo.get_cpu_vendors_map(when=self.when), | |
205 | "clock_speed_avg" : avg, | |
206 | "clock_speed_stddev" : stddev, | |
207 | "clock_speed_min" : min, | |
208 | "clock_speed_max" : max, | |
209 | } | |
210 | ||
211 | return self.render("fireinfo/stats-cpus.html", **data) | |
212 | ||
213 | ||
96c9bb79 | 214 | class StatsProcessorDetailHandler(BaseHandler): |
66862195 MT |
215 | def get(self, platform): |
216 | assert platform in ("arm", "x86") | |
217 | ||
218 | flags = self.fireinfo.get_common_cpu_flags_by_platform(platform, when=self.when) | |
219 | ||
220 | return self.render("fireinfo/stats-cpus-detail.html", | |
221 | platform=platform, flags=flags) | |
222 | ||
223 | ||
96c9bb79 | 224 | class StatsMemoryHandler(BaseHandler): |
66862195 MT |
225 | def get(self): |
226 | avg, stddev, min, max = self.fireinfo.get_memory_amounts(when=self.when) | |
227 | amounts = self.fireinfo.get_common_memory_amounts(when=self.when) | |
228 | ||
229 | data = { | |
230 | "mem_avg" : avg, | |
231 | "mem_stddev" : stddev, | |
232 | "mem_min" : min, | |
233 | "mem_max" : max, | |
234 | "amounts" : amounts, | |
235 | } | |
236 | ||
237 | return self.render("fireinfo/stats-memory.html", **data) | |
238 | ||
239 | ||
96c9bb79 | 240 | class StatsReleasesHandler(BaseHandler): |
66862195 MT |
241 | def get(self): |
242 | data = { | |
243 | "releases" : self.fireinfo.get_releases_map(when=self.when), | |
244 | "kernels" : self.fireinfo.get_kernels_map(when=self.when), | |
245 | } | |
246 | return self.render("fireinfo/stats-oses.html", **data) | |
247 | ||
248 | ||
96c9bb79 | 249 | class StatsVirtualHandler(BaseHandler): |
66862195 MT |
250 | def get(self): |
251 | data = { | |
252 | "hypervisors" : self.fireinfo.get_hypervisor_map(when=self.when), | |
253 | "virtual" : self.fireinfo.get_virtual_ratio(when=self.when), | |
254 | } | |
255 | ||
256 | return self.render("fireinfo/stats-virtual.html", **data) | |
257 | ||
258 | ||
96c9bb79 | 259 | class StatsGeoHandler(BaseHandler): |
66862195 MT |
260 | def get(self): |
261 | return self.render("fireinfo/stats-geo.html", | |
262 | geo_locations = self.fireinfo.get_geo_location_map(when=self.when)) | |
263 | ||
264 | ||
96c9bb79 | 265 | class StatsLanguagesHandler(BaseHandler): |
66862195 MT |
266 | def get(self): |
267 | return self.render("fireinfo/stats-languages.html", | |
268 | languages = self.fireinfo.get_language_map(when=self.when)) | |
269 | ||
270 | ||
96c9bb79 | 271 | class StatsNetworkingHandler(BaseHandler): |
66862195 MT |
272 | def get(self): |
273 | network=self.fireinfo.get_network_zones_map(when=self.when) | |
274 | ||
275 | return self.render("fireinfo/stats-network.html", network=network) | |
276 | ||
277 | ||
96c9bb79 | 278 | class DeviceVendorHandler(BaseHandler): |
66862195 MT |
279 | def get(self, subsystem, vendor_id): |
280 | devices = self.fireinfo.get_devices_by_vendor(subsystem, vendor_id, | |
281 | when=self.when) | |
282 | ||
283 | vendor_name = self.fireinfo.get_vendor_string(subsystem, vendor_id) | |
284 | ||
285 | self.render("fireinfo/vendor-detail.html", vendor_name=vendor_name, | |
286 | devices=devices) | |
287 | ||
288 | ||
96c9bb79 | 289 | class DeviceVendorCompatHandler(BaseHandler): |
66862195 MT |
290 | def get(self, subsystem, vendor_id): |
291 | self.redirect("/device/%s/%s" % (subsystem, vendor_id)) | |
292 | ||
293 | ||
96c9bb79 | 294 | class DeviceModelHandler(BaseHandler): |
66862195 MT |
295 | def get(self, subsystem, vendor, model): |
296 | percentage = self.fireinfo.get_device_percentage(subsystem, vendor, | |
297 | model, when=self.when) | |
298 | percentage *= 100 | |
299 | ||
300 | profiles = self.fireinfo.get_device_in_profile(subsystem, vendor, | |
301 | model, when=self.when) | |
302 | ||
303 | vendor_name = self.fireinfo.get_vendor_string(subsystem, vendor) | |
304 | model_name = self.fireinfo.get_model_string(subsystem, vendor, model) | |
305 | ||
306 | self.render("fireinfo/model-detail.html", profiles=profiles, | |
307 | vendor_id=vendor, vendor_name=vendor_name, | |
308 | model_id=model, model_name=model_name, | |
309 | percentage=percentage, subsystem=subsystem) | |
310 | ||
311 | ||
96c9bb79 | 312 | class DeviceModelCompatHandler(BaseHandler): |
66862195 MT |
313 | def get(self, subsystem, vendor_id, model_id): |
314 | self.redirect("/device/%s/%s/%s" % (subsystem, vendor_id, model_id)) | |
315 | ||
316 | ||
96c9bb79 | 317 | class AdminFireinfoHandler(BaseHandler): |
66862195 MT |
318 | def get(self): |
319 | profiles_with_data, profiles_all = self.fireinfo.get_active_profiles(when=self.when) | |
320 | ||
321 | data = { | |
322 | "archive_size" : self.fireinfo.get_archive_size(when=self.when), | |
de14b297 | 323 | "total_updates" : self.fireinfo.get_total_updates_count(when=self.when), |
66862195 MT |
324 | "profiles_with_data" : profiles_with_data, |
325 | "profiles_all" : profiles_all, | |
326 | } | |
327 | ||
328 | self.render("fireinfo/stats-admin.html", **data) | |
96c9bb79 MT |
329 | |
330 | ||
331 | class DeviceTableModule(ui_modules.UIModule): | |
332 | def render(self, devices): | |
333 | return self.render_string("fireinfo/modules/table-devices.html", | |
334 | devices=devices) | |
335 | ||
336 | ||
337 | class DeviceAndGroupsTableModule(ui_modules.UIModule): | |
338 | def render(self, devices): | |
339 | _ = self.locale.translate | |
340 | ||
341 | groups = {} | |
342 | ||
343 | for device in devices: | |
344 | if device.cls not in groups: | |
345 | groups[device.cls] = [] | |
346 | ||
347 | groups[device.cls].append(device) | |
348 | ||
349 | # Sort all devices | |
350 | for key in list(groups.keys()): | |
351 | groups[key].sort() | |
352 | ||
353 | # Order the groups by their name | |
354 | groups = list(groups.items()) | |
355 | groups.sort() | |
356 | ||
357 | return self.render_string("fireinfo/modules/table-devices-and-groups.html", | |
358 | groups=groups) | |
359 | ||
360 | ||
361 | class GeoTableModule(ui_modules.UIModule): | |
362 | def render(self, items): | |
363 | countries = [] | |
364 | other_countries = [] | |
365 | for code, value in items: | |
366 | # Skip the satellite providers in this ranking | |
367 | if code in (None, "A1", "A2"): | |
368 | continue | |
369 | ||
370 | name = self.backend.geoip.get_country_name(code) | |
371 | ||
372 | # Don't add countries with a small share on the list | |
373 | if value < 0.01: | |
374 | other_countries.append(name) | |
375 | continue | |
376 | ||
377 | country = database.Row({ | |
378 | "code" : code, | |
379 | "name" : name, | |
380 | "value" : value, | |
381 | }) | |
382 | countries.append(country) | |
383 | ||
384 | return self.render_string("fireinfo/modules/table-geo.html", | |
385 | countries=countries, other_countries=other_countries) |