]> git.ipfire.org Git - ipfire.org.git/blame - www/webapp/backend/stasy.py
fireinfo: Only query profiles that do really exist.
[ipfire.org.git] / www / webapp / backend / stasy.py
CommitLineData
372efc19
MT
1#!/usr/bin/python
2
91a446f0
MT
3from __future__ import division
4
372efc19
MT
5import hwdata
6import logging
7import pymongo
8
9from misc import Singleton
10
11DATABASE_HOST = ["irma.ipfire.org", "madeye.ipfire.org"]
12DATABASE_NAME = "stasy"
13
91a446f0 14CPU_SPEED_CONSTRAINTS = (0, 500, 1000, 1500, 2000, 2500, 3000, 3500)
372efc19
MT
15MEMORY_CONSTRAINTS = (0, 64, 128, 256, 512, 1024, 2048, 4096, 8128, 16384)
16
17class ProfileDict(object):
18 def __init__(self, data):
19 self._data = data
20
21
22class ProfileCPU(ProfileDict):
23 @property
24 def arch(self):
25 return self._data.get("arch")
26
27 @property
28 def vendor(self):
29 return self._data.get("vendor")
30
91a446f0
MT
31 @property
32 def speed(self):
33 return self._data.get("speed")
34
35 @property
36 def bogomips(self):
37 return self._data.get("bogomips")
38
372efc19
MT
39 @property
40 def model(self):
41 return self._data.get("model")
42
43 @property
44 def model_string(self):
45 return self._data.get("model_string")
46
47 @property
48 def flags(self):
49 return self._data.get("flags")
50
91a446f0
MT
51 @property
52 def count(self):
53 return self._data.get("count")
54
372efc19
MT
55 @property
56 def capable_64bit(self):
57 return "lm" in self.flags
58
59 @property
60 def capable_pae(self):
61 return "pae" in self.flags
62
63 @property
64 def capable_virt(self):
65 return "vmx" in self.flags or "svm" in self.flags
66
67
68class ProfileHypervisor(ProfileDict):
69 def __repr__(self):
70 return "<%s %s-%s>" % (self.__class__.__name__, self.vendor, self.type)
71
72 @property
73 def vendor(self):
74 return self._data.get("vendor")
75
372efc19
MT
76 @property
77 def type(self):
78 return self._data.get("type")
79
80
81class ProfileDevice(ProfileDict):
82 subsystem2class = {
83 "pci" : hwdata.PCI,
84 "usb" : hwdata.USB,
85 }
86
91a446f0
MT
87 classid2name = {
88 "pci" : {
bc19cabd 89 "00" : "Unclassified",
91a446f0
MT
90 "01" : "Mass storage",
91 "02" : "Network",
92 "03" : "Display",
93 "04" : "Multimedia",
94 "05" : "Memory controller",
95 "06" : "Bridge",
96 "07" : "Communication",
97 "08" : "Generic system peripheral",
98 "09" : "Input device",
99 "0a" : "Docking station",
100 "0b" : "Processor",
101 "0c" : "Serial bus",
102 "0d" : "Wireless",
103 "0e" : "Intelligent controller",
104 "0f" : "Satellite communications controller",
bc19cabd 105 "10" : "Encryption",
91a446f0
MT
106 "11" : "Signal processing controller",
107 "ff" : "Unassigned class",
108 },
109
110 "usb" : {
bc19cabd 111 "00" : "Unclassified",
91a446f0
MT
112 "01" : "Multimedia",
113 "02" : "Communication",
114 "03" : "Input device",
bc19cabd
MT
115 "05" : "Generic system peripheral",
116 "06" : "Image",
91a446f0
MT
117 "07" : "Printer",
118 "08" : "Mass storage",
119 "09" : "Hub",
bc19cabd
MT
120 "0a" : "Communication",
121 "0b" : "Smart card",
122 "0d" : "Encryption",
91a446f0 123 "0e" : "Display",
bc19cabd 124 "0f" : "Personal Healthcare",
91a446f0
MT
125 "dc" : "Diagnostic Device",
126 "e0" : "Wireless",
bc19cabd
MT
127 "ef" : "Unclassified",
128 "fe" : "Unclassified",
129 "ff" : "Unclassified",
91a446f0
MT
130 }
131 }
132
133 def __cmp__(self, other):
134 return cmp(self.vendor, other.vendor) or \
135 cmp(self.model, other.model) or \
136 cmp(self.driver, other.driver)
137
372efc19
MT
138 @property
139 def model(self):
140 return self._data.get("model")
141
142 @property
143 def model_string(self):
144 cls = self.subsystem2class[self.subsystem]
145
146 return cls().get_device(self.vendor, self.model)
147
148 @property
149 def subsystem(self):
150 return self._data.get("subsystem")
151
152 @property
153 def vendor(self):
154 return self._data.get("vendor")
155
156 @property
157 def vendor_string(self):
158 cls = self.subsystem2class[self.subsystem]
159
160 return cls().get_vendor(self.vendor)
161
162 @property
163 def driver(self):
164 return self._data.get("driver")
165
91a446f0
MT
166 @property
167 def cls(self):
168 classid = self._data.get("deviceclass")
169
170 if self.subsystem == "pci":
171 classid = classid[:-4]
172 if len(classid) == 1:
173 classid = "0%s" % classid
174
175 elif self.subsystem == "usb" and classid:
176 classid = classid.split("/")[0]
177 classid = "%02x" % int(classid)
178
179 try:
180 return self.classid2name[self.subsystem][classid]
181 except KeyError:
182 return "N/A"
183
372efc19
MT
184
185class Profile(ProfileDict):
91a446f0
MT
186 def __init__(self, profile_blob):
187 ProfileDict.__init__(self, profile_blob.get("profile"))
372efc19 188
91a446f0
MT
189 self.public_id = profile_blob.get("public_id")
190 self.updated = profile_blob.get("updated")
191 self._geoip = profile_blob.get("geoip", None)
372efc19
MT
192
193 def __repr__(self):
194 return "<%s %s>" % (self.__class__.__name__, self.public_id)
195
196 @property
197 def cpu(self):
198 return ProfileCPU(self._data.get("cpu"))
199
200 @property
201 def devices(self):
202 devices = []
203 for d in self._data.get("devices"):
204 d = ProfileDevice(d)
205
91a446f0 206 if d.driver in ("pcieport", "usb", "hub"):
372efc19
MT
207 continue
208
209 devices.append(d)
210
211 return devices
212
213 @property
214 def hypervisor(self):
215 if self.virtual:
216 return ProfileHypervisor(self._data.get("hypervisor"))
217
218 @property
219 def virtual(self):
220 return self.system.get("virtual")
221
222 @property
223 def system(self):
224 return self._data.get("system")
225
226 @property
227 def release(self):
228 return self.system.get("release")
229
230 @property
231 def kernel(self):
232 return self.system.get("kernel_release")
233
234 @property
235 def memory(self):
236 return self.system.get("memory")
237
238 @property
239 def root_size(self):
91a446f0
MT
240 return self.system.get("root_size") or 0
241
242 @property
243 def vendor(self):
244 return self.system.get("vendor")
245
246 @property
247 def model(self):
248 return self.system.get("model")
249
250 @property
251 def country_code(self):
252 if self._geoip:
253 return self._geoip["country_code"].lower()
372efc19 254
91a446f0 255 return "unknown"
372efc19
MT
256
257
258class Stasy(object):
259 __metaclass__ = Singleton
260
261 def __init__(self):
262 # Initialize database connection
263 self._conn = pymongo.Connection(DATABASE_HOST)
264 self._db = self._conn[DATABASE_NAME]
265
266 def get_profile_count(self):
267 # XXX need to implement something to get profiles updated since
268 # a given date
269
270 # All distinct profiles (based on public_id)
271 return self._db.profiles.distinct("public_id").count()
272
273 #def _get_profile_cursor(self, public_id):
274 # c = self._db.profiles.find({ "public_id" : public_id })
275 # c.sort("updated", pymongo.ASCENDING)
276 #
277 # return c
278
91a446f0 279 def profile_exists(self, public_id):
910be037 280 return self.query({ "public_id" : public_id }).count() >= 1
91a446f0 281
372efc19
MT
282 def get_profile(self, public_id):
283 # XXX should only find one object in the end
910be037 284 for p in self.query({ "public_id" : public_id }):
91a446f0 285 p = Profile(p)
372efc19
MT
286
287 return p
288
289 def get_profiles(self):
290 # XXX needs nicer database query
291 profiles = []
292 for p in self._db.profiles.find():
293 if not p.get("public_id") in profiles:
294 profiles.append(p.get("public_id"))
295
296 return profiles
297
910be037
MT
298 def query(self, query, archives=False, no_virt=False, all=False):
299 db = self._db.profiles
300
301 if archives:
302 db = self.db.archives
303
304 if not all:
305 # XXX cannot use the index?
306 query.update({ "profile" : { "$exists" : True }})
307
308 if no_virt:
309 query.update({ "profile.system.virtual" : False })
310
311 logging.debug("Executing query: %s" % query)
312
313 return db.find(query)
314
372efc19
MT
315 @property
316 def secret_ids(self):
317 return self._db.profiles.distinct("secret_id")
318
319 @property
320 def cpus(self):
910be037 321 return self.query({}, no_virt=True).distinct("profile.cpu")
372efc19
MT
322
323 @property
324 def cpu_vendors(self):
910be037 325 return self.query({}, no_virt=True).distinct("profile.cpu.vendor")
372efc19
MT
326
327 @property
91a446f0 328 def cpu_vendors_map(self):
372efc19
MT
329 cpus = {}
330
331 for vendor in self.cpu_vendors:
332 cpus[vendor] = \
910be037 333 self.query({
372efc19 334 "profile.cpu.vendor" : vendor
910be037 335 }, no_virt=True).count()
372efc19
MT
336
337 return cpus
338
339 @property
91a446f0
MT
340 def cpu_speed_average(self):
341 speed = 0
342
910be037 343 all = self.query({}, no_virt=True)
91a446f0
MT
344
345 # XXX ugly. needs to be done by group()
346 for m in all:
347 if not m.has_key("profile"):
348 continue
349 speed += m.get("profile").get("cpu").get("speed")
350
351 return (speed / all.count())
352
353 @property
354 def cpu_speed_map(self):
355 cpu_speeds = {}
356
357 for i in range(len(CPU_SPEED_CONSTRAINTS) - 1):
358 min, max = CPU_SPEED_CONSTRAINTS[i:i+2]
359
360 cpu_speeds[min, max] = \
910be037
MT
361 self.query({
362 "profile.cpu.speed" : {
91a446f0
MT
363 "$gte" : min, "$lt" : max
364 }
910be037 365 }, no_virt=True).count()
91a446f0
MT
366
367 return cpu_speeds
368
369 def get_memory_map(self):
372efc19
MT
370 memory = {}
371
372 for i in range(len(MEMORY_CONSTRAINTS) - 1):
373 min, max = MEMORY_CONSTRAINTS[i:i+2]
374
375 memory[min, max] = \
910be037 376 self.query(
372efc19
MT
377 { "profile.system.memory" : {
378 "$gte" : min * 1024, "$lt" : max * 1024
379 }
910be037 380 }, no_virt=True).count()
372efc19
MT
381
382 return memory
383
384 @property
385 def memory_average(self):
386 memory = 0
387
910be037 388 all = self.query({}, no_virt=True)
372efc19
MT
389
390 # XXX ugly. needs to be done by group()
391 for m in all:
392 if not m.has_key("profile"):
393 continue
394 memory += int(m.get("profile").get("system").get("memory"))
395
396 return (memory / all.count()) / 1024
397
398 @property
399 def hypervisor_vendors(self):
910be037 400 return self.query({}).distinct("profile.hypervisor.vendor")
372efc19
MT
401
402 @property
403 def hypervisor_map(self):
404 hypervisors = {}
405
406 for hypervisor in self.hypervisor_vendors:
407 hypervisors[hypervisor] = \
910be037 408 self.query({
372efc19
MT
409 "profile.hypervisor.vendor" : hypervisor
410 }).count()
411
412 return hypervisors
413
414 @property
415 def hypervisor_models(self):
910be037 416 return self.query({}).distinct("profile.hypervisor.model")
372efc19
MT
417
418 @property
419 def virtual_map(self):
420 virtual = {
421 True: None,
422 False: None,
423 }
424
425 for k in virtual.keys():
426 virtual[k] = \
910be037 427 self.query({ "profile.system.virtual": k }).count()
372efc19
MT
428
429 return virtual
430
431 @property
432 def languages(self):
910be037 433 return self.query({}).distinct("profile.system.language")
372efc19 434
91a446f0
MT
435 def get_language_map(self):
436 languages = {}
437
438 for language in self.languages:
439 languages[language] = \
910be037 440 self.query({
91a446f0
MT
441 "profile.system.language" : language
442 }).count()
443
444 return languages
445
372efc19
MT
446 @property
447 def vendors(self):
910be037 448 return self.query({}).distinct("profile.system.vendor")
372efc19
MT
449
450 @property
451 def vendor_map(self):
452 vendors = {}
453
454 for vendor in self.vendors:
455 vendors[vendor] = \
910be037 456 self.query({
372efc19
MT
457 "profile.system.vendor" : vendor
458 }).count()
459
460 return vendors
461
462 @property
463 def models(self):
910be037 464 return self.query({}).distinct("profile.system.model")
372efc19 465
91a446f0
MT
466 @property
467 def model_map(self):
468 models = {}
469
470 for model in self.models:
471 models[model] = \
910be037 472 self.query({
91a446f0
MT
473 "profile.system.model" : model
474 }).count()
475
476 return models
477
478 @property
479 def arches(self):
910be037 480 return self.query({}).distinct("profile.cpu.arch")
91a446f0
MT
481
482 @property
483 def arch_map(self):
484 arches = {}
485
486 for arch in self.arches:
487 arches[arch] = \
910be037 488 self.query({
91a446f0
MT
489 "profile.cpu.arch" : arch
490 }).count()
491
492 return arches
493
494 @property
495 def kernels(self):
910be037 496 return self.query({}).distinct("profile.system.kernel_release")
91a446f0
MT
497
498 @property
499 def kernel_map(self):
500 kernels = {}
501
502 for kernel in self.kernels:
503 kernels[kernel] = \
910be037 504 self.query({
91a446f0
MT
505 "profile.system.kernel_release" : kernel
506 }).count()
507
508 return kernels
509
510 @property
511 def releases(self):
910be037 512 return self.query({}).distinct("profile.system.release")
91a446f0
MT
513
514 @property
515 def release_map(self):
516 releases = {}
517
518 for release in self.releases:
519 releases[release] = \
910be037 520 self.query({
91a446f0
MT
521 "profile.system.release" : release
522 }).count()
523
524 return releases
525
526 def get_device_percentage(self, bus, vendor_id, model_id):
910be037 527 profiles_with_device = self.query({
91a446f0
MT
528 "profile.devices.subsystem" : bus,
529 "profile.devices.vendor" : vendor_id,
530 "profile.devices.model" : model_id,
531 })
532
910be037 533 profiles_all = self.query({})
91a446f0
MT
534
535 if not profiles_all.count():
536 return 0
537
538 return profiles_with_device.count() / profiles_all.count()
539
910be037
MT
540 def get_cpu_flag_map(self, flags):
541 # XXX needs a cleanup
91a446f0 542
910be037 543 _flags = { True : 0 }
91a446f0 544
910be037
MT
545 if type(flags) == type("a"):
546 flags = [flags]
547
548 for flag in flags:
549 _flags[True] += \
550 self.query({
551 "profile.cpu.flags" : flag,
552 }, no_virt=True).count()
91a446f0 553
910be037 554 _flags[False] = self.query({}, no_virt=True).count() - _flags[True]
91a446f0 555
910be037 556 return _flags
91a446f0
MT
557
558 @property
559 def geo_locations(self):
910be037 560 return [code.lower() for code in self.query({}).distinct("geoip.country_code")]
91a446f0
MT
561
562 def get_geo_location_map(self):
563 geo_locations = {}
564
565 count = 0
566 for geo_location in self.geo_locations:
567 geo_locations[geo_location] = \
910be037 568 self.query({
91a446f0
MT
569 "geoip.country_code" : geo_location
570 }).count()
571
572 count += geo_locations[geo_location]
573
574 # XXX must only be systems that have profile data
910be037 575 profiles_all = self.query({})
91a446f0
MT
576
577 unknown_count = profiles_all.count() - count
578 if unknown_count:
579 geo_locations["unknown"] = unknown_count
580
581 return geo_locations
582
583 def get_models_by_vendor(self, subsystem, vendor_id):
584 devices = []
585
586 # XXX must only be systems that have profile data
910be037 587 profiles_all = self.query({})
91a446f0
MT
588
589 for profile in profiles_all:
590 if not profile.has_key("profile"):
591 continue
592
593 profile = Profile(profile)
594
595 for device in profile.devices:
596 if not device.vendor == vendor_id:
597 continue
598
599 if not device in devices:
600 devices.append(device)
601
602 return devices
372efc19
MT
603
604
605if __name__ == "__main__":
606 s = Stasy()
607
608 print s.get_profile("0" * 40)
609 print s.cpu_vendors
610 for id in s.secret_ids:
611 print "\t", id
612
613 #for p in s._db.profiles.find():
614 # print p
615
616 print s.cpu_map
617 print s.memory_map
618 print s.memory_average
619 print s.hypervisor_vendors
620 print s.hypervisor_models
621 print s.languages
91a446f0 622 print s.language_maps
372efc19
MT
623 print s.vendors
624 print s.vendor_map
625 print s.models
626 print s.cpus
91a446f0
MT
627 print s.cpu_map
628 print s.arches
629 print s.arch_map
630 print s.kernels
631 print s.kernel_map
632 print s.releases
633 print s.release_map
372efc19
MT
634
635 p = s.get_profile("0b5f4fe2162fdfbfa29b632610e317078fa70d34")
91a446f0
MT
636 print p._data
637# print p.hypervisor
638