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