]> git.ipfire.org Git - people/shoehn/ipfire.org.git/blame - webapp/backend/fireinfo.py
Replace hostname replacement function and make it better accessible
[people/shoehn/ipfire.org.git] / webapp / backend / fireinfo.py
CommitLineData
66862195
MT
1#!/usr/bin/python
2
3from __future__ import division
4
5import datetime
6import hwdata
7import logging
8import re
9
10import util
11from misc import Object
12
13N_ = lambda x: x
14
15CPU_VENDORS = {
16 "AMDisbetter!" : "AMD",
17 "AuthenticAMD" : "AMD",
18 "CentaurHauls" : "VIA",
19 "CyrixInstead" : "Cyrix",
20 "GenuineIntel" : "Intel",
21 "TransmetaCPU" : "Transmeta",
22 "GenuineTMx86" : "Transmeta",
23 "Geode by NSC" : "NSC",
24 "NexGenDriven" : "NexGen",
25 "RiseRiseRise" : "Rise",
26 "SiS SiS SiS" : "SiS",
27 "SiS SiS SiS " : "SiS",
28 "UMC UMC UMC " : "UMC",
29 "VIA VIA VIA " : "VIA",
30 "Vortex86 SoC" : "Vortex86",
31}
32
33CPU_STRINGS = (
34 # AMD
35 (r"(AMD Athlon).*(XP).*", r"\1 \2"),
36 (r"(AMD Phenom).* ([0-9]+) .*", r"\1 \2"),
37 (r"(AMD Phenom).*", r"\1"),
38 (r"(AMD Sempron).*", r"\1"),
39 (r"AMD Athlon.* II X2 ([a-z0-9]+).*", r"AMD Athlon X2 \1"),
40 (r"(Geode).*", r"\1"),
41
42 # Intel
43 (r"Intel\(R\) (Atom|Celeron).*CPU\s*([A-Z0-9]+) .*", r"Intel \1 \2"),
44 (r"(Intel).*(Celeron).*", r"\1 \2"),
45 (r"Intel.* Core.*2 Duo *CPU .* ([A-Z0-9]+) .*", r"Intel C2D \1"),
46 (r"Intel.* Core.*2 CPU .* ([A-Z0-9]+) .*", r"Intel C2 \1"),
47 (r"Intel.* Core.*2 Quad *CPU .* ([A-Z0-9]+) .*", r"Intel C2Q \1"),
48 (r"Intel.* Xeon.* CPU .* ([A-Z0-9]+) .*", r"Intel Xeon \1"),
49 (r"(Intel).*(Xeon).*", r"\1 \2"),
50 (r"Intel.* Pentium.* (D|4) .*", r"Intel Pentium \1"),
51 (r"Intel.* Pentium.* Dual .* ([A-Z0-9]+) .*", r"Intel Pentium Dual \1"),
52 (r"Pentium.* Dual-Core .* ([A-Z0-9]+) .*", r"Intel Pentium Dual \1"),
53 (r"(Pentium I{2,3}).*", r"Intel \1"),
54 (r"(Celeron \(Coppermine\))", r"Intel Celeron"),
55
56 # VIA
57 (r"(VIA \w*).*", r"\1"),
58
59 # Qemu
60 (r"QEMU Virtual CPU version .*", r"QEMU CPU"),
61
62 # ARM
63 (r"Feroceon .*", r"ARM Feroceon"),
64)
65
66IGNORED_DEVICES = ["usb",]
67
68class ProfileDict(object):
69 def __init__(self, data):
70 self._data = data
71
72
73class ProfileNetwork(ProfileDict):
74 def __eq__(self, other):
95ae21d1
MT
75 if other is None:
76 return False
77
66862195
MT
78 if not self.has_red == other.has_red:
79 return False
80
81 if not self.has_green == other.has_green:
82 return False
83
84 if not self.has_orange == other.has_orange:
85 return False
86
87 if not self.has_blue == other.has_blue:
88 return False
89
90 return True
91
92 def __iter__(self):
93 ret = []
94
95 for zone in ("red", "green", "orange", "blue"):
96 if self.has_zone(zone):
97 ret.append(zone)
98
99 return iter(ret)
100
101 def has_zone(self, name):
102 return self._data.get("has_%s" % name)
103
104 @property
105 def has_red(self):
106 return self._data.get("has_red", False)
107
108 @property
109 def has_green(self):
110 return self._data.get("has_green", False)
111
112 @property
113 def has_orange(self):
114 return self._data.get("has_orange", False)
115
116 @property
117 def has_blue(self):
118 return self._data.get("has_blue", False)
119
120
121class Processor(Object):
122 def __init__(self, backend, id, data=None, clock_speed=None, bogomips=None):
123 Object.__init__(self, backend)
124
125 self.id = id
126 self.__data = data
127 self.__clock_speed = clock_speed
128 self.__bogomips = bogomips
129
130 def __str__(self):
131 s = []
132
133 if not self.model_string.startswith(self.vendor):
134 s.append(self.vendor)
135 s.append("-")
136
137 s.append(self.model_string)
138
139 if self.core_count > 1:
140 s.append("x%s" % self.core_count)
141
142 return " ".join(s)
143
144 @property
145 def data(self):
146 if self.__data is None:
147 self.__data = self.db.get("SELECT * FROM fireinfo_processors \
148 WHERE id = %s", self.id)
149
150 return self.__data
151
152 @property
153 def vendor(self):
154 try:
155 return CPU_VENDORS[self.data.vendor]
156 except KeyError:
157 return self.data.vendor
158
159 @property
160 def model(self):
161 return self.data.model
162
163 @property
164 def model_string(self):
165 s = self.data.model_string.split()
166
167 return " ".join((e for e in s if e))
168
169 @property
170 def flags(self):
171 return self.data.flags
172
173 def has_flag(self, flag):
174 return flag in self.flags
175
176 def uses_ht(self):
177 return self.has_flag("ht")
178
179 @property
180 def core_count(self):
181 return self.data.core_count
182
183 @property
184 def count(self):
185 if self.uses_ht():
186 return self.core_count // 2
187
188 return self.core_count
189
190 @property
191 def clock_speed(self):
192 return self.__clock_speed
193
194 def format_clock_speed(self):
195 if not self.clock_speed:
196 return
197
198 if self.clock_speed < 1000:
199 return "%dMHz" % self.clock_speed
200
201 return "%.2fGHz" % round(self.clock_speed / 1000, 2)
202
203 @property
204 def bogomips(self):
205 return self.__bogomips
206
207 @property
208 def capabilities(self):
209 caps = [
210 ("64bit", self.has_flag("lm")),
211 ("aes", self.has_flag("aes")),
212 ("nx", self.has_flag("nx")),
213 ("pae", self.has_flag("pae") or self.has_flag("lpae")),
214 ("rdrand", self.has_flag("rdrand")),
215 ]
216
217 # If the system is already running in a virtual environment,
218 # we cannot correctly detect if the CPU supports svm or vmx
219 if self.has_flag("hypervisor"):
220 caps.append(("virt", None))
221 else:
222 caps.append(("virt", self.has_flag("vmx") or self.has_flag("svm")))
223
224 return caps
225
226 def format_model(self):
227 s = self.model_string
228 for pattern, repl in CPU_STRINGS:
229 if re.match(pattern, s) is None:
230 continue
231 return re.sub(pattern, repl, s)
232
233 # Otherwise remove the symbols
234 for i in ("C", "R", "TM"):
235 s = s.replace("(%s)" % i, "")
236
237 return s
238
239 @property
240 def friendly_string(self):
241 s = []
242
243 model = self.format_model()
244 s.append(model)
245
246 clock_speed = self.format_clock_speed()
247 if clock_speed:
248 s.append("@ %s" % clock_speed)
249
250 if self.count > 1:
251 s.append("x%s" % self.count)
252
253 return " ".join(s)
254
255
256class Device(Object):
257 classid2name = {
258 "pci" : {
259 "00" : N_("Unclassified"),
260 "01" : N_("Mass storage"),
261 "02" : N_("Network"),
262 "03" : N_("Display"),
263 "04" : N_("Multimedia"),
264 "05" : N_("Memory controller"),
265 "06" : N_("Bridge"),
266 "07" : N_("Communication"),
267 "08" : N_("Generic system peripheral"),
268 "09" : N_("Input device"),
269 "0a" : N_("Docking station"),
270 "0b" : N_("Processor"),
271 "0c" : N_("Serial bus"),
272 "0d" : N_("Wireless"),
273 "0e" : N_("Intelligent controller"),
274 "0f" : N_("Satellite communications controller"),
275 "10" : N_("Encryption"),
276 "11" : N_("Signal processing controller"),
277 "ff" : N_("Unassigned class"),
278 },
279
280 "usb" : {
281 "00" : N_("Unclassified"),
282 "01" : N_("Multimedia"),
283 "02" : N_("Communication"),
284 "03" : N_("Input device"),
285 "05" : N_("Generic system peripheral"),
286 "06" : N_("Image"),
287 "07" : N_("Printer"),
288 "08" : N_("Mass storage"),
289 "09" : N_("Hub"),
290 "0a" : N_("Communication"),
291 "0b" : N_("Smart card"),
292 "0d" : N_("Encryption"),
293 "0e" : N_("Display"),
294 "0f" : N_("Personal Healthcare"),
295 "dc" : N_("Diagnostic Device"),
296 "e0" : N_("Wireless"),
297 "ef" : N_("Unclassified"),
298 "fe" : N_("Unclassified"),
299 "ff" : N_("Unclassified"),
300 }
301 }
302
303 def __init__(self, backend, id, data=None):
304 Object.__init__(self, backend)
305
306 self.id = id
307 self.__data = data
308
309 def __repr__(self):
310 return "<%s vendor=%s model=%s>" % (self.__class__.__name__,
311 self.vendor_string, self.model_string)
312
313 def __cmp__(self, other):
314 if self.id and self.id == other.id:
315 return 0
316
317 return \
318 cmp(self.subsystem, other.subsystem) or \
319 cmp(self.vendor_string, other.vendor_string) or \
320 cmp(self.vendor, other.vendor) or \
321 cmp(self.model_string, other.model_string) or \
322 cmp(self.model, other.model) or \
323 cmp(self.driver, other.driver)
324
325 @property
326 def data(self):
327 if self.__data is None:
328 assert self.id
329
330 self.__data = self.db.get("SELECT * FROM fireinfo_devices \
331 WHERE id = %s", self.id)
332
333 return self.__data
334
335 def is_showable(self):
336 if self.driver in IGNORED_DEVICES:
337 return False
338
339 if self.driver in ("pcieport", "hub"):
340 return False
341
342 return True
343
344 @property
345 def subsystem(self):
346 return self.data.subsystem
347
348 @property
349 def model(self):
350 return self.data.model
351
352 @property
353 def model_string(self):
354 return self.fireinfo.get_model_string(self.subsystem,
355 self.vendor, self.model)
356
357 @property
358 def vendor(self):
359 return self.data.vendor
360
361 @property
362 def vendor_string(self):
363 return self.fireinfo.get_vendor_string(self.subsystem, self.vendor)
364
365 @property
366 def driver(self):
367 return self.data.driver
368
369 @property
370 def cls(self):
371 classid = self.data.deviceclass
372
373 if self.subsystem == "pci":
374 classid = classid[:-4]
375 if len(classid) == 1:
376 classid = "0%s" % classid
377
378 elif self.subsystem == "usb" and classid:
379 classid = classid.split("/")[0]
380 classid = "%02x" % int(classid)
381
382 try:
383 return self.classid2name[self.subsystem][classid]
384 except KeyError:
385 return "N/A"
386
387 @property
388 def percentage(self):
389 return self.data.get("percentage", None)
390
391
392class Profile(Object):
393 def __init__(self, backend, id, data=None):
394 Object.__init__(self, backend)
395
396 self.id = id
397 self.__data = data
398
399 def __repr__(self):
400 return "<%s %s>" % (self.__class__.__name__, self.public_id)
401
402 def __cmp__(self, other):
403 return cmp(self.id, other.id)
404
405 def is_showable(self):
406 if self.arch_id:
407 return True
408
409 return False
410
411 @property
412 def data(self):
413 if self.__data is None:
414 self.__data = self.db.get("SELECT * FROM fireinfo_profiles \
415 WHERE id = %s", self.id)
416
417 return self.__data
418
419 @property
420 def public_id(self):
421 return self.data.public_id
422
423 @property
424 def private_id(self):
425 raise NotImplementedError
426
427 @property
428 def time_created(self):
429 return self.data.time_created
430
431 @property
432 def time_updated(self):
433 return self.data.time_updated
434
435 def updated(self, profile_parser=None, location=None, when=None):
436 valid = self.settings.get_int("fireinfo_profile_days_valid", 14)
437
438 self.db.execute("UPDATE fireinfo_profiles \
439 SET \
440 time_updated = then_or_now(%s), \
441 time_valid = then_or_now(%s) + INTERVAL '%s days', \
442 updates = updates + 1 \
443 WHERE id = %s", when, when, valid, self.id)
444
445 if profile_parser:
446 self.set_processor_speeds(
447 profile_parser.processor_clock_speed,
448 profile_parser.processor_bogomips,
449 )
450
451 if location:
452 self.set_location(location)
453
454 def expired(self, when=None):
455 self.db.execute("UPDATE fireinfo_profiles \
456 SET time_valid = then_or_now(%s) WHERE id = %s", when, self.id)
457
458 def parse(self, parser):
459 # Processor
460 self.processor = parser.processor
461 self.set_processor_speeds(parser.processor_clock_speed, parser.processor_bogomips)
462
463 # All devices
464 self.devices = parser.devices
465
466 # System
467 self.system_id = parser.system_id
468
469 # Memory
470 self.memory = parser.memory
471
472 # Storage
473 self.storage = parser.storage
474
475 # Kernel
476 self.kernel_id = parser.kernel_id
477
478 # Arch
479 self.arch_id = parser.arch_id
480
481 # Release
482 self.release_id = parser.release_id
483
484 # Language
485 self.language = parser.language
486
487 # Virtual
488 if parser.virtual:
489 self.hypervisor_id = parser.hypervisor_id
490
491 # Network
492 self.network = parser.network
493
494 # Location
495
496 def get_location(self):
497 if not hasattr(self, "_location"):
498 res = self.db.get("SELECT location FROM fireinfo_profiles_locations \
499 WHERE profile_id = %s", self.id)
500
501 if res:
502 self._location = res.location
503 else:
504 self._location = None
505
506 return self._location
507
508 def set_location(self, location):
509 if self.location == location:
510 return
511
512 self.db.execute("DELETE FROM fireinfo_profiles_locations \
513 WHERE profile_id = %s", self.id)
514 self.db.execute("INSERT INTO fireinfo_profiles_locations(profile_id, location) \
515 VALUES(%s, %s)", self.id, location)
516
517 self._location = location
518
519 location = property(get_location, set_location)
520
521 @property
522 def location_string(self):
523 return self.geoip.get_country_name(self.location)
524
525 # Devices
526
527 @property
528 def device_ids(self):
529 if not hasattr(self, "_device_ids"):
530 res = self.db.query("SELECT device_id FROM fireinfo_profiles_devices \
531 WHERE profile_id = %s", self.id)
532
533 self._device_ids = sorted([r.device_id for r in res])
534
535 return self._device_ids
536
537 def get_devices(self):
538 if not hasattr(self, "_devices"):
539 res = self.db.query("SELECT * FROM fireinfo_devices \
540 LEFT JOIN fireinfo_profiles_devices ON \
541 fireinfo_devices.id = fireinfo_profiles_devices.device_id \
542 WHERE fireinfo_profiles_devices.profile_id = %s", self.id)
543
544 self._devices = []
545 for row in res:
546 device = Device(self.backend, row.id, row)
547 self._devices.append(device)
548
549 return self._devices
550
551 def set_devices(self, devices):
552 device_ids = [d.id for d in devices]
553
554 self.db.execute("DELETE FROM fireinfo_profiles_devices WHERE profile_id = %s", self.id)
555 self.db.executemany("INSERT INTO fireinfo_profiles_devices(profile_id, device_id) \
556 VALUES(%s, %s)", ((self.id, d) for d in device_ids))
557
558 self._devices = devices
559 self._device_ids = device_ids
560
561 devices = property(get_devices, set_devices)
562
4238ef83 563 def count_device(self, subsystem, vendor, model):
66862195
MT
564 counter = 0
565
566 for dev in self.devices:
4238ef83 567 if dev.subsystem == subsystem and dev.vendor == vendor and dev.model == model:
66862195
MT
568 counter += 1
569
570 return counter
571
572 # System
573
574 def get_system_id(self):
575 if not hasattr(self, "_system_id"):
576 res = self.db.get("SELECT system_id AS id FROM fireinfo_profiles_systems \
577 WHERE profile_id = %s", self.id)
578
579 if res:
580 self._system_id = res.id
581 else:
582 self._system_id = None
583
584 return self._system_id
585
586 def set_system_id(self, system_id):
587 self.db.execute("DELETE FROM fireinfo_profiles_systems WHERE profile_id = %s", self.id)
588
589 if system_id:
590 self.db.execute("INSERT INTO fireinfo_profiles_systems(profile_id, system_id) \
591 VALUES(%s, %s)", self.id, system_id)
592
593 self._system_id = None
594 if hasattr(self, "_system"):
595 del self._system
596
597 system_id = property(get_system_id, set_system_id)
598
599 @property
600 def system(self):
601 if not hasattr(self, "_system"):
602 res = self.db.get("SELECT fireinfo_systems.vendor AS vendor, fireinfo_systems.model AS model \
603 FROM fireinfo_profiles_systems \
604 LEFT JOIN fireinfo_systems ON fireinfo_profiles_systems.system_id = fireinfo_systems.id \
605 WHERE fireinfo_profiles_systems.profile_id = %s", self.id)
606
607 if res:
608 self._system = (res.vendor, res.model)
609 else:
610 self._system = (None, None)
611
612 return self._system
613
614 @property
615 def system_vendor(self):
616 try:
617 v, m = self.system
618 return v
619 except TypeError:
620 pass
621
622 @property
623 def system_model(self):
624 try:
625 v, m = self.system
626 return m
627 except TypeError:
628 pass
629
630 @property
631 def appliance_id(self):
632 if not hasattr(self, "_appliance_id"):
633 appliances = (
4238ef83 634 ("fountainnetworks-prime", self._appliance_test_fountainnetworks_prime),
66862195
MT
635 ("lightningwirelabs-eco", self._appliance_test_lightningwirelabs_eco),
636 )
637
638 self._appliance_id = None
639 for name, test_func in appliances:
640 if not test_func():
641 continue
642
643 self._appliance_id = name
644 break
645
646 return self._appliance_id
647
648 @property
649 def appliance(self):
4238ef83
MT
650 if self.appliance_id == "fountainnetworks-prime":
651 return "Fountain Networks - IPFire Prime Box"
652
653 elif self.appliance_id == "lightningwirelabs-eco":
654 return "Lightning Wire Labs - IPFire Eco Appliance"
655
656 def _appliance_test_fountainnetworks_prime(self):
9fd4f970 657 if not self.system in (("SECO", None), ("SECO", "0949")):
4238ef83
MT
658 return False
659
660 # Must have a wireless device
661 if self.count_device("usb", "148f", "5572") < 1:
662 return False
663
664 return True
66862195
MT
665
666 def _appliance_test_lightningwirelabs_eco(self):
667 if not self.system == ("MSI", "MS-9877"):
668 return False
669
670 # Must have four Intel network adapters
4238ef83 671 network_adapters_count = self.count_device("pci", "8086", "10d3")
66862195
MT
672 if not network_adapters_count == 4:
673 return False
674
675 # 4GB of memory
676 if not self.memory >= 4230823936 * 0.95:
677 return False
678
679 return True
680
681 # Processors
682
683 @property
684 def processor_id(self):
685 if hasattr(self, "_processor"):
686 return self._processor.id
687
688 if not hasattr(self, "_processor_id"):
689 res = self.db.get("SELECT processor_id FROM fireinfo_profiles_processors \
690 WHERE profile_id = %s", self.id)
691
692 if res:
693 self._processor_id = res.processor_id
694 else:
695 self._processor_id = None
696
697 return self._processor_id
698
699 def get_processor(self):
700 if not self.processor_id:
701 return
702
703 if not hasattr(self, "_processor"):
704 res = self.db.get("SELECT * FROM fireinfo_profiles_processors \
705 WHERE profile_id = %s", self.id)
706
707 if res:
708 self._processor = self.fireinfo.get_processor_by_id(res.processor_id,
709 clock_speed=res.clock_speed, bogomips=res.bogomips)
710 else:
711 self._processor = None
712
713 return self._processor
714
715 def set_processor(self, processor):
716 self.db.execute("DELETE FROM fireinfo_profiles_processors \
717 WHERE profile_id = %s", self.id)
718
719 if processor:
720 self.db.execute("INSERT INTO fireinfo_profiles_processors(profile_id, processor_id) \
721 VALUES(%s, %s)", self.id, processor.id)
722
723 self._processor = processor
724
725 processor = property(get_processor, set_processor)
726
727 def set_processor_speeds(self, clock_speed, bogomips):
728 self.db.execute("UPDATE fireinfo_profiles_processors \
729 SET clock_speed = %s, bogomips = %s WHERE profile_id = %s",
730 clock_speed, bogomips, self.id)
731
732 # Compat
733 @property
734 def cpu(self):
735 return self.processor
736
737 # Memory
738
739 def get_memory(self):
740 if not hasattr(self, "_memory"):
741 res = self.db.get("SELECT amount FROM fireinfo_profiles_memory \
742 WHERE profile_id = %s", self.id)
743
744 if res:
745 self._memory = res.amount * 1024
746 else:
747 self._memory = None
748
749 return self._memory
750
751 def set_memory(self, amount):
752 if self.memory == amount:
753 return
754
755 amount /= 1024
756
757 self.db.execute("DELETE FROM fireinfo_profiles_memory WHERE profile_id = %s", self.id)
758 if amount:
759 self.db.execute("INSERT INTO fireinfo_profiles_memory(profile_id, amount) \
760 VALUES(%s, %s)", self.id, amount)
761
762 self._memory = amount * 1024
763
764 memory = property(get_memory, set_memory)
765
766 @property
767 def friendly_memory(self):
768 return util.format_size(self.memory)
769
770 # Storage
771
772 def get_storage(self):
773 if not hasattr(self, "_storage"):
774 res = self.db.get("SELECT amount FROM fireinfo_profiles_storage \
775 WHERE profile_id = %s", self.id)
776
777 if res:
778 self._storage = res.amount * 1024
779 else:
780 self._storage = None
781
782 return self._storage
783
784 def set_storage(self, amount):
785 if self.storage == amount:
786 return
787
788 amount /= 1024
789
790 self.db.execute("DELETE FROM fireinfo_profiles_storage WHERE profile_id = %s", self.id)
791 if amount:
792 self.db.execute("INSERT INTO fireinfo_profiles_storage(profile_id, amount) \
793 VALUES(%s, %s)", self.id, amount)
794
795 self._storage = amount * 1024
796
797 storage = property(get_storage, set_storage)
798
799 @property
800 def friendly_storage(self):
801 return util.format_size(self.storage)
802
803 # Kernel
804
805 def get_kernel_id(self):
806 if not hasattr(self, "_kernel_id"):
807 res = self.db.get("SELECT fireinfo_profiles_kernels.kernel_id AS id FROM fireinfo_profiles \
808 LEFT JOIN fireinfo_profiles_kernels ON fireinfo_profiles.id = fireinfo_profiles_kernels.profile_id \
809 WHERE fireinfo_profiles.id = %s", self.id)
810
811 if res:
812 self._kernel_id = res.id
813 else:
814 self._kernel_id = None
815
816 return self._kernel_id
817
818 def set_kernel_id(self, kernel_id):
819 if self.kernel_id == kernel_id:
820 return
821
822 self.db.execute("DELETE FROM fireinfo_profiles_kernels WHERE profile_id = %s", self.id)
823 if kernel_id:
824 self.db.execute("INSERT INTO fireinfo_profiles_kernels(profile_id, kernel_id) \
825 VALUES(%s, %s)", self.id, kernel_id)
826
827 self._kernel_id = kernel_id
828 if hasattr(self, "_kernel"):
829 del self._kernel
830
831 kernel_id = property(get_kernel_id, set_kernel_id)
832
833 @property
834 def kernel(self):
835 if not hasattr(self, "_kernel"):
836 res = self.db.get("SELECT fireinfo_kernels.name AS name FROM fireinfo_profiles \
837 LEFT JOIN fireinfo_profiles_kernels ON fireinfo_profiles.id = fireinfo_profiles_kernels.profile_id \
838 LEFT JOIN fireinfo_kernels ON fireinfo_kernels.id = fireinfo_profiles_kernels.kernel_id \
839 WHERE fireinfo_profiles.id = %s", self.id)
840
841 if res:
842 self._kernel = res.name
843 else:
844 self._kernel = None
845
846 return self._kernel
847
848 # Arch
849
850 def get_arch_id(self):
851 if not hasattr(self, "_arch_id"):
852 res = self.db.get("SELECT fireinfo_profiles_arches.arch_id AS id FROM fireinfo_profiles \
853 LEFT JOIN fireinfo_profiles_arches ON fireinfo_profiles.id = fireinfo_profiles_arches.profile_id \
854 WHERE fireinfo_profiles.id = %s", self.id)
855
856 if res:
857 self._arch_id = res.id
858 else:
859 self._arch_id = None
860
861 return self._arch_id
862
863 def set_arch_id(self, arch_id):
864 if self.arch_id == arch_id:
865 return
866
867 self.db.execute("DELETE FROM fireinfo_profiles_arches WHERE profile_id = %s", self.id)
868 if arch_id:
869 self.db.execute("INSERT INTO fireinfo_profiles_arches(profile_id, arch_id) \
870 VALUES(%s, %s)", self.id, arch_id)
871
872 self._arch_id = None
873 if hasattr(self, "_arch"):
874 del self._arch
875
876 arch_id = property(get_arch_id, set_arch_id)
877
878 @property
879 def arch(self):
880 if not hasattr(self, "_arch"):
881 res = self.db.get("SELECT fireinfo_arches.name AS name FROM fireinfo_profiles \
882 LEFT JOIN fireinfo_profiles_arches ON fireinfo_profiles.id = fireinfo_profiles_arches.profile_id \
883 LEFT JOIN fireinfo_arches ON fireinfo_arches.id = fireinfo_profiles_arches.arch_id \
884 WHERE fireinfo_profiles.id = %s", self.id)
885
886 if res:
887 self._arch = res.name
888 else:
889 self._arch = None
890
891 return self._arch
892
893 # Release
894
895 def get_release_id(self):
896 if not hasattr(self, "_release_id"):
897 res = self.db.get("SELECT fireinfo_profiles_releases.release_id AS id FROM fireinfo_profiles \
898 LEFT JOIN fireinfo_profiles_releases ON fireinfo_profiles.id = fireinfo_profiles_releases.profile_id \
899 WHERE fireinfo_profiles.id = %s", self.id)
900
901 if res:
902 self._release_id = res.id
903 else:
904 self._release_id = None
905
906 return self._release_id
907
908 def set_release_id(self, release_id):
909 if self.release_id == release_id:
910 return
911
912 self.db.execute("DELETE FROM fireinfo_profiles_releases WHERE profile_id = %s", self.id)
913 if release_id:
914 self.db.execute("INSERT INTO fireinfo_profiles_releases(profile_id, release_id) \
915 VALUES(%s, %s)", self.id, release_id)
916
917 self._release_id = release_id
918 if hasattr(self, "_release"):
919 del self._release
920
921 release_id = property(get_release_id, set_release_id)
922
923 @property
924 def release(self):
925 if not hasattr(self, "_release"):
926 res = self.db.get("SELECT fireinfo_releases.name AS name FROM fireinfo_profiles \
927 LEFT JOIN fireinfo_profiles_releases ON fireinfo_profiles.id = fireinfo_profiles_releases.profile_id \
928 LEFT JOIN fireinfo_releases ON fireinfo_profiles_releases.release_id = fireinfo_releases.id \
929 WHERE fireinfo_profiles.id = %s", self.id)
930
931 if res:
932 self._release = self._format_release(res.name)
933 else:
934 self._release = None
935
936 return self._release
937
938 @staticmethod
939 def _format_release(r):
940 if not r:
941 return r
942
943 # Remove the development header
944 r = r.replace("Development Build: ", "")
945
946 pairs = (
947 ("-beta", " - Beta "),
948 ("-rc", " - Release Candidate "),
949 ("rc", "Release Candidate "),
950 ("core", "Core Update "),
951 ("beta", "Beta "),
952 )
953
954 for k, v in pairs:
955 r = r.replace(k, v)
956
957 return r
958
959 # Virtual
960
961 @property
962 def virtual(self):
963 if not hasattr(self, "_virtual"):
964 res = self.db.get("SELECT 1 FROM fireinfo_profiles_virtual \
965 WHERE profile_id = %s", self.id)
966
967 if res:
968 self._virtual = True
969 else:
970 self._virtual = False
971
972 return self._virtual
973
974 def get_hypervisor_id(self):
975 if not hasattr(self, "_hypervisor_id"):
976 res = self.db.get("SELECT fireinfo_profiles_virtual.hypervisor_id AS id FROM fireinfo_profiles \
977 LEFT JOIN fireinfo_profiles_virtual ON fireinfo_profiles.id = fireinfo_profiles_virtual.profile_id \
978 WHERE fireinfo_profiles.id = %s", self.id)
979
980 if res:
981 self._hypervisor_id = res.id
982 else:
983 self._hypervisor_id = None
984
985 return self._hypervisor_id
986
987 def set_hypervisor_id(self, hypervisor_id):
988 self.db.execute("DELETE FROM fireinfo_profiles_virtual WHERE profile_id = %s", self.id)
989 self.db.execute("INSERT INTO fireinfo_profiles_virtual(profile_id, hypervisor_id) \
990 VALUES(%s, %s)", self.id, hypervisor_id)
991
992 self._hypervisor_id = hypervisor_id
993
994 hypervisor_id = property(get_hypervisor_id, set_hypervisor_id)
995
996 @property
997 def hypervisor(self):
998 if not hasattr(self, "_hypervisor"):
999 res = self.db.get("SELECT fireinfo_hypervisors.name AS hypervisor FROM fireinfo_profiles \
1000 LEFT JOIN fireinfo_profiles_virtual ON fireinfo_profiles.id = fireinfo_profiles_virtual.profile_id \
1001 LEFT JOIN fireinfo_hypervisors ON fireinfo_profiles_virtual.hypervisor_id = fireinfo_hypervisors.id \
1002 WHERE fireinfo_profiles.id = %s", self.id)
1003
1004 if res:
1005 self._hypervisor = res.hypervisor
1006 else:
1007 self._hypervisor = None
1008
1009 return self._hypervisor
1010
1011 # Language
1012
1013 def get_language(self):
1014 if not hasattr(self, "_language"):
1015 res = self.db.get("SELECT language FROM fireinfo_profiles_languages \
1016 WHERE profile_id = %s", self.id)
1017
1018 if res:
1019 self._language = res.language
1020 else:
1021 self._language = None
1022
1023 return self._language
1024
1025 def set_language(self, language):
1026 self.db.execute("DELETE FROM fireinfo_profiles_languages WHERE profile_id = %s", self.id)
1027
1028 if language:
1029 self.db.execute("INSERT INTO fireinfo_profiles_languages(profile_id, language) \
1030 VALUES(%s, %s)", self.id, language)
1031
1032 self._language = language
1033
1034 language = property(get_language, set_language)
1035
1036 # Network
1037
1038 def get_network(self):
1039 if not hasattr(self, "_network"):
1040 res = self.db.get("SELECT * FROM fireinfo_profiles_networks \
1041 WHERE profile_id = %s", self.id)
1042
1043 if not res:
1044 res = {}
1045
1046 self._network = ProfileNetwork(res)
1047
1048 return self._network
1049
1050 def set_network(self, network):
1051 self.db.execute("DELETE FROM fireinfo_profiles_networks WHERE profile_id = %s", self.id)
1052
1053 if network:
1054 self.db.execute("INSERT INTO fireinfo_profiles_networks(profile_id, \
1055 has_red, has_green, has_orange, has_blue) VALUES(%s, %s, %s, %s, %s)",
1056 self.id, network.has_red, network.has_green, network.has_orange, network.has_blue)
1057
1058 self._network = network
1059
1060 network = property(get_network, set_network)
1061
1062
1063class ProfileData(Object):
1064 def __init__(self, backend, id, data=None, profile=None):
1065 Object.__init__(self, backend)
1066
1067 self.id = id
1068 self._data = data
1069 self._profile = profile
1070
1071 @property
1072 def data(self):
1073 if self._data is None:
1074 self._data = self.db.get("SELECT * FROM fireinfo_profile_data \
1075 WHERE id = %s", self.id)
1076
1077 return self._data
1078
1079 @property
1080 def profile(self):
1081 if not self._profile:
1082 self._profile = self.fireinfo.get_profile_by_id(self.profile_id)
1083
1084 return self._profile
1085
1086 @property
1087 def profile_id(self):
1088 return self.data.profile_id
1089
1090
1091class ProfileParserError(Exception):
1092 pass
1093
1094
1095class ProfileParser(Object):
1096 __device_args = (
1097 "subsystem",
1098 "vendor",
1099 "model",
1100 "sub_vendor",
1101 "sub_model",
1102 "driver",
1103 "deviceclass",
1104 )
1105
1106 __processor_args = (
1107 "vendor",
1108 "model_string",
1109 "family",
1110 "model",
1111 "stepping",
1112 "core_count",
1113 "flags",
1114 )
1115
1116 __processor_args_mandatory = (
1117 "vendor",
1118 "model_string",
1119 )
1120
1121 def __init__(self, backend, public_id, blob=None):
1122 Object.__init__(self, backend)
1123
1124 self.public_id = public_id
1125 self.private_id = None
1126 self.devices = []
1127 self.processor = None
1128 self.processor_clock_speed = None
1129 self.processor_bogomips = None
1130 self.system_id = None
1131 self.memory = None
1132 self.storage = None
1133 self.kernel = None
1134 self.kernel_id = None
1135 self.arch = None
1136 self.arch_id = None
1137 self.release = None
1138 self.release_id = None
1139 self.language = None
1140 self.virtual = None
1141 self.hypervisor_id = None
1142 self.network = None
1143
1144 self.__parse_blob(blob)
1145
1146 def equals(self, other):
1147 if not self.processor_id == other.processor_id:
1148 return False
1149
1150 if not self.device_ids == other.device_ids:
1151 return False
1152
1153 if not self.system_id == other.system_id:
1154 return False
1155
1156 if not self.memory == other.memory:
1157 return False
1158
1159 if not self.storage == other.storage:
1160 return False
1161
1162 if not self.kernel_id == other.kernel_id:
1163 return False
1164
1165 if not self.arch_id == other.arch_id:
1166 return False
1167
1168 if not self.release_id == other.release_id:
1169 return False
1170
1171 if not self.language == other.language:
1172 return False
1173
1174 if not self.virtual == other.virtual:
1175 return False
1176
1177 if other.virtual:
1178 if not self.hypervisor_id == other.hypervisor_id:
1179 return False
1180
1181 if not self.network == other.network:
1182 return False
1183
1184 return True
1185
1186 def __parse_blob(self, blob):
1187 _profile = blob.get("profile", {})
1188 self.private_id = blob.get("private_id")
1189
1190 # Do not try to parse an empty profile
1191 if not _profile:
1192 return
1193
1194 # Processor
1195 _processor = _profile.get("cpu", {})
1196 self.__parse_processor(_processor)
1197
1198 # Find devices
1199 _devices = _profile.get("devices", [])
1200 self.__parse_devices(_devices)
1201
1202 # System
1203 _system = _profile.get("system")
1204 if _system:
1205 self.__parse_system(_system)
1206
1207 # Memory (convert to bytes)
1208 memory = _system.get("memory", None)
1209 if memory:
1210 self.memory = memory * 1024
1211
1212 # Storage size (convert to bytes)
1213 storage = _system.get("root_size", None)
1214 if storage:
1215 self.storage = storage * 1024
1216
1217 # Kernel
1218 kernel = _system.get("kernel_release", None)
1219 if kernel:
1220 self.__parse_kernel(kernel)
1221
1222 # Release
1223 release = _system.get("release", None)
1224 if release:
1225 self.__parse_release(release)
1226
1227 # Language
1228 language = _system.get("language", None)
1229 if language:
1230 self.__parse_language(language)
1231
1232 # Virtual
1233 self.virtual = _system.get("virtual", False)
1234 if self.virtual:
1235 hypervisor = _profile.get("hypervisor")
1236 self.__parse_hypervisor(hypervisor)
1237
1238 # Network
1239 _network = _profile.get("network")
1240 if _network:
1241 self.__parse_network(_network)
1242
1243 @property
1244 def device_ids(self):
1245 return sorted([d.id for d in self.devices])
1246
1247 def __parse_devices(self, _devices):
1248 self.devices = []
1249
1250 for _device in _devices:
1251 args = {}
1252 for arg in self.__device_args:
1253 args[arg] = _device.get(arg, None)
1254
95ae21d1
MT
1255 # Skip if the subsystem is not set
1256 if not args.get("subsystem", None):
1257 continue
1258
66862195
MT
1259 # Find the device or create a new one.
1260 device = self.fireinfo.get_device(**args)
1261 if not device:
1262 device = self.fireinfo.create_device(**args)
1263
1264 self.devices.append(device)
1265
1266 def __parse_system(self, system):
1267 vendor = system.get("vendor", None)
1268 if not vendor:
1269 vendor = None
1270
1271 model = system.get("model", None)
1272 if not model:
1273 model = None
1274
1275 self.system_id = self.fireinfo.get_system(vendor, model)
1276 if not self.system_id:
1277 self.system_id = self.fireinfo.create_system(vendor, model)
1278
1279 @property
1280 def processor_id(self):
1281 if not self.processor:
1282 return
1283
1284 return self.processor.id
1285
1286 def __parse_processor(self, _processor):
1287 args = {}
1288 for arg in self.__processor_args:
1289 if arg == "core_count":
1290 _arg = "count"
1291 else:
1292 _arg = arg
1293
1294 args[arg] = _processor.get(_arg, None)
1295
1296 for arg in self.__processor_args_mandatory:
1297 if not args.get(arg, None):
1298 raise ProfileParserError, "Mandatory argument missing: %s" % arg
1299
1300 self.processor = self.fireinfo.get_processor(**args)
1301 if not self.processor:
1302 self.processor = self.fireinfo.create_processor(**args)
1303
1304 self.processor_clock_speed = _processor.get("speed", None)
1305 self.processor_bogomips = _processor.get("bogomips", None)
1306
1307 arch = _processor.get("arch", None)
1308 if arch:
1309 self.__parse_arch(arch)
1310
1311 def __parse_kernel(self, kernel):
1312 self.kernel_id = self.fireinfo.get_kernel(kernel)
1313 if not self.kernel_id:
1314 self.kernel_id = self.fireinfo.create_kernel(kernel)
1315 assert self.kernel_id
1316
1317 self.kernel = kernel
1318
1319 def __parse_arch(self, arch):
1320 self.arch_id = self.fireinfo.get_arch(arch)
1321 if not self.arch_id:
1322 self.arch_id = self.fireinfo.create_arch(arch)
1323
1324 self.arch = arch
1325
1326 def __parse_release(self, release):
1327 # Remove the arch bit
1328 if release:
1329 r = [e for e in release.split() if e]
1330 for s in ("(i586)", "(armv5tel)"):
1331 try:
1332 r.remove(s)
1333 break
1334 except ValueError:
1335 pass
1336
1337 release = " ".join(r)
1338
1339 self.release_id = self.fireinfo.get_release(release)
1340 if not self.release_id:
1341 self.release_id = self.fireinfo.create_release(release)
1342 assert self.release_id
1343
1344 self.release = release
1345
1346 def __parse_language(self, language):
1347 self.language, delim, rest = language.partition(".")
1348 self.language, delim, rest = language.partition("_")
1349
1350 def __parse_hypervisor(self, hypervisor):
1351 vendor = hypervisor.get("vendor", "other")
1352
1353 if vendor in ("other", "unknown"):
1354 self.hypervisor_id = None
1355 return
1356
1357 self.hypervisor_id = self.fireinfo.get_hypervisor(vendor)
1358 if not self.hypervisor_id:
1359 self.hypervisor_id = self.fireinfo.create_hypervisor(vendor)
1360
1361 def __parse_network(self, network):
1362 self.network = ProfileNetwork({
1363 "has_red" : network.get("red", False),
1364 "has_green" : network.get("green", False),
1365 "has_orange" : network.get("orange", False),
1366 "has_blue" : network.get("blue", False),
1367 })
1368
1369
1370class Fireinfo(Object):
1371 def get_profile_count(self, when=None):
1372 res = self.db.get("SELECT COUNT(*) AS count FROM fireinfo_profiles \
1373 WHERE then_or_now(%s) BETWEEN time_created AND time_valid", when)
1374
1375 if res:
1376 return res.count
1377
de14b297
MT
1378 def get_total_updates_count(self, when=None):
1379 res = self.db.get("SELECT COUNT(*) + SUM(updates) AS count \
1380 FROM fireinfo_profiles WHERE time_created <= then_or_now(%s)", when)
66862195
MT
1381
1382 if res:
1383 return res.count
1384
1385 # Parser
1386
1387 def parse_profile(self, public_id, blob):
1388 return ProfileParser(self.backend, public_id, blob)
1389
1390 # Profiles
1391
1392 def profile_exists(self, public_id):
1393 res = self.db.get("SELECT id FROM fireinfo_profiles \
1394 WHERE public_id = %s LIMIT 1", public_id)
1395
1396 if res:
1397 return True
1398
1399 return False
1400
1401 def can_update_profile(self, public_id, private_id, when=None):
1402 res = self.db.get("SELECT 1 FROM fireinfo_profiles \
1403 WHERE public_id = %s AND private_id = %s \
1404 AND time_updated + INTERVAL '60 minutes' <= then_or_now(%s) \
1405 AND time_valid >= then_or_now(%s) ORDER BY time_updated DESC LIMIT 1",
1406 public_id, private_id, when, when)
1407
1408 if res:
1409 return True
1410
1411 return False
1412
1413 def get_profile(self, public_id, private_id=None, when=None):
1414 res = self.db.get("SELECT * FROM fireinfo_profiles \
1415 WHERE public_id = %s AND \
1416 (CASE WHEN %s IS NULL THEN TRUE ELSE private_id = %s END) AND \
1417 (CASE WHEN %s IS NULL THEN TRUE ELSE \
1418 then_or_now(%s) BETWEEN time_created AND time_valid END) \
1419 ORDER BY time_updated DESC LIMIT 1", public_id,
1420 private_id, private_id, when, when)
1421
1422 if res:
1423 return Profile(self.backend, res.id, res)
1424
1425 def get_profiles(self, public_id):
1426 res = self.db.query("SELECT * FROM fireinfo_profiles \
1427 WHERE public_id = %s ORDER BY time_created DESC", public_id)
1428
1429 profiles = []
1430 for row in res:
1431 profile = Profile(self.backend, row.id, row)
1432 profiles.append(profile)
1433
1434 return profiles
1435
1436 def create_profile(self, public_id, private_id, when=None):
1437 valid = self.settings.get_int("fireinfo_profile_days_valid", 14)
1438
1439 res = self.db.get("INSERT INTO fireinfo_profiles(public_id, private_id, \
1440 time_created, time_updated, time_valid) VALUES(%s, %s, then_or_now(%s), \
1441 then_or_now(%s), then_or_now(%s) + INTERVAL '%s days') RETURNING id",
1442 public_id, private_id, when, when, when, valid)
1443
1444 if res:
1445 return Profile(self.backend, res.id)
1446
1447 # Devices
1448
1449 def create_device(self, subsystem, vendor, model, sub_vendor=None, sub_model=None,
1450 driver=None, deviceclass=None):
1451 res = self.db.get("INSERT INTO fireinfo_devices(subsystem, vendor, model, \
1452 sub_vendor, sub_model, driver, deviceclass) VALUES(%s, %s, %s, %s, %s, %s, %s) \
1453 RETURNING id", subsystem, vendor, model, sub_vendor, sub_model, driver, deviceclass)
1454
1455 if res:
1456 return Device(self.backend, res.id)
1457
1458 def get_device(self, subsystem, vendor, model, sub_vendor=None, sub_model=None,
1459 driver=None, deviceclass=None):
1460 res = self.db.get("SELECT * FROM fireinfo_devices \
1461 WHERE subsystem = %s AND vendor = %s AND model = %s \
1462 AND sub_vendor IS NOT DISTINCT FROM %s \
1463 AND sub_model IS NOT DISTINCT FROM %s \
1464 AND driver IS NOT DISTINCT FROM %s \
1465 AND deviceclass IS NOT DISTINCT FROM %s \
1466 LIMIT 1", subsystem, vendor, model, sub_vendor,
1467 sub_model, driver, deviceclass)
1468
1469 if res:
1470 return Device(self.backend, res.id, res)
1471
1472 # System
1473
1474 def create_system(self, vendor, model):
1475 res = self.db.get("INSERT INTO fireinfo_systems(vendor, model) \
1476 VALUES(%s, %s) RETURNING id", vendor, model)
1477
1478 if res:
1479 return res.id
1480
1481 def get_system(self, vendor, model):
1482 res = self.db.get("SELECT id FROM fireinfo_systems WHERE vendor IS NOT DISTINCT FROM %s \
1483 AND model IS NOT DISTINCT FROM %s LIMIT 1", vendor, model)
1484
1485 if res:
1486 return res.id
1487
1488 # Processors
1489
1490 def create_processor(self, vendor, model_string, family, model, stepping, core_count, flags=None):
1491 res = self.db.get("INSERT INTO fireinfo_processors(vendor, model_string, \
1492 family, model, stepping, core_count, flags) VALUES(%s, %s, %s, %s, %s, %s, %s) \
1493 RETURNING id", vendor, model_string, family, model, stepping, core_count, flags)
1494
1495 if res:
1496 return Processor(self.backend, res.id)
1497
1498 def get_processor_by_id(self, processor_id, **kwargs):
1499 res = self.db.get("SELECT * FROM fireinfo_processors \
1500 WHERE id = %s", processor_id)
1501
1502 if res:
1503 return Processor(self.backend, res.id, data=res, **kwargs)
1504
1505 def get_processor(self, vendor, model_string, family, model, stepping, core_count, flags=None):
1506 if flags is None:
1507 flags = []
1508
1509 res = self.db.get("SELECT * FROM fireinfo_processors \
1510 WHERE vendor = %s AND model_string = %s \
1511 AND family IS NOT DISTINCT FROM %s AND model IS NOT DISTINCT FROM %s \
1512 AND stepping IS NOT DISTINCT FROM %s AND core_count = %s \
1513 AND flags <@ %s AND flags @> %s", vendor, model_string, family, model,
1514 stepping, core_count, flags, flags)
1515
1516 if res:
1517 return Processor(self.backend, res.id, res)
1518
1519 # Kernel
1520
1521 def create_kernel(self, kernel):
1522 res = self.db.get("INSERT INTO fireinfo_kernels(name) VALUES(%s) \
1523 RETURNING id", kernel)
1524
1525 if res:
1526 return res.id
1527
1528 def get_kernel(self, kernel):
1529 res = self.db.get("SELECT id FROM fireinfo_kernels WHERE name = %s", kernel)
1530
1531 if res:
1532 return res.id
1533
1534 # Arch
1535
1536 def create_arch(self, arch):
1537 res = self.db.get("INSERT INTO fireinfo_arches(name) VALUES(%s) \
1538 RETURNING id", arch)
1539
1540 if res:
1541 return res.id
1542
1543 def get_arch(self, arch):
1544 res = self.db.get("SELECT id FROM fireinfo_arches WHERE name = %s", arch)
1545
1546 if res:
1547 return res.id
1548
1549 # Release
1550
1551 def create_release(self, release):
1552 res = self.db.get("INSERT INTO fireinfo_releases(name) VALUES(%s) \
1553 RETURNING id", release)
1554
1555 if res:
1556 return res.id
1557
1558 def get_release(self, release):
1559 res = self.db.get("SELECT id FROM fireinfo_releases WHERE name = %s", release)
1560
1561 if res:
1562 return res.id
1563
1564 # Hypervisor
1565
1566 def create_hypervisor(self, hypervisor):
1567 res = self.db.get("INSERT INTO fireinfo_hypervisors(name) VALUES(%s) \
1568 RETURNING id", hypervisor)
1569
1570 if res:
1571 return res.id
1572
1573 def get_hypervisor(self, hypervisor):
1574 res = self.db.get("SELECT id FROM fireinfo_hypervisors WHERE name = %s",
1575 hypervisor)
1576
1577 if res:
1578 return res.id
1579
1580 # Handle profile
1581
1582 def handle_profile(self, *args, **kwargs):
1583 self.db.execute("START TRANSACTION")
1584
1585 # Wrap all the handling of the profile in a huge transaction.
1586 try:
1587 self._handle_profile(*args, **kwargs)
1588
1589 except:
1590 self.db.execute("ROLLBACK")
1591 raise
1592
1593 else:
1594 self.db.execute("COMMIT")
1595
1596 def _handle_profile(self, public_id, profile_blob, location=None, when=None):
1597 private_id = profile_blob.get("private_id", None)
1598 assert private_id
1599
1600 # Check if the profile already exists in the database.
1601 profile = self.fireinfo.get_profile(public_id, private_id=private_id, when=when)
1602
1603 # Check if the update can actually be updated
1604 if profile and not self.fireinfo.can_update_profile(public_id, private_id, when=when):
1605 logging.warning("Profile not updated because private ID does not"
1606 " match or last update is too recent: %s" % public_id)
1607 return
1608
1609 # Parse the profile
1610 profile_parser = self.parse_profile(public_id, profile_blob)
1611
1612 # If a profile exists, check if it matches and if so, just update the
1613 # timestamp.
1614 if profile:
1615 # Check if the profile has changed. If so, update the data.
1616 if profile_parser.equals(profile):
1617 profile.updated(profile_parser, location=location, when=when)
1618 return
1619
1620 # If it does not match, we assume that it is expired and
1621 # create a new profile.
1622 profile.expired(when=when)
1623
1624 # Replace the old profile with a new one
1625 profile = self.fireinfo.create_profile(public_id, private_id, when=when)
1626 profile.parse(profile_parser)
1627
1628 if location:
1629 profile.set_location(location)
1630
1631 return profile
1632
1633 # Data outputs
1634
1635 def get_random_profile(self, when=None):
1636 # Check if the architecture exists so that we pick a profile with some data
1637 res = self.db.get("SELECT public_id FROM fireinfo_profiles \
1638 LEFT JOIN fireinfo_profiles_arches ON fireinfo_profiles.id = fireinfo_profiles_arches.profile_id \
1639 WHERE fireinfo_profiles_arches.profile_id IS NOT NULL \
1640 AND then_or_now(%s) BETWEEN time_created AND time_valid ORDER BY RANDOM() LIMIT 1", when)
1641
1642 if res:
1643 return res.public_id
1644
1645 def get_active_profiles(self, when=None):
1646 res = self.db.get("WITH profiles AS (SELECT fireinfo_profiles_at(%s) AS id) \
1647 SELECT COUNT(*) AS with_data, (SELECT COUNT(*) FROM profiles) AS count FROM profiles \
1648 LEFT JOIN fireinfo_profiles_releases ON profiles.id = fireinfo_profiles_releases.profile_id \
1649 WHERE fireinfo_profiles_releases.profile_id IS NOT NULL", when)
1650
1651 if res:
1652 return res.with_data, res.count
1653
1654 def get_archive_size(self, when=None):
1655 res = self.db.get("SELECT COUNT(*) AS count FROM fireinfo_profiles \
1656 WHERE time_created <= then_or_now(%s)", when)
1657
1658 if res:
1659 return res.count
1660
1661 def get_geo_location_map(self, when=None):
1662 res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_at(%s) AS id) \
1663 SELECT location, COUNT(location)::float / (SELECT COUNT(*) FROM profiles) AS count FROM profiles \
1664 LEFT JOIN fireinfo_profiles_locations ON profiles.id = fireinfo_profiles_locations.profile_id \
1665 WHERE fireinfo_profiles_locations.location IS NOT NULL GROUP BY location ORDER BY count DESC", when)
1666
1667 return ((r.location, r.count) for r in res)
1668
1669 def get_language_map(self, when=None):
1670 res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_at(%s) AS id) \
1671 SELECT language, COUNT(language)::float / (SELECT COUNT(*) FROM profiles) AS count FROM profiles \
1672 LEFT JOIN fireinfo_profiles_languages ON profiles.id = fireinfo_profiles_languages.profile_id \
1673 WHERE fireinfo_profiles_languages.language IS NOT NULL GROUP BY language ORDER BY count DESC", when)
1674
1675 return ((r.language, r.count) for r in res)
1676
1677 @property
1678 def cpu_vendors(self):
1679 res = self.db.query("SELECT DISTINCT vendor FROM fireinfo_processors ORDER BY vendor")
1680
1681 return (CPU_VENDORS.get(r.vendor, r.vendor) for r in res)
1682
1683 def get_cpu_vendors_map(self, when=None):
1684 res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
1685 SELECT vendor, COUNT(vendor)::float / (SELECT COUNT(*) FROM profiles) AS count FROM profiles \
1686 LEFT JOIN fireinfo_profiles_processors ON profiles.id = fireinfo_profiles_processors.profile_id \
1687 LEFT JOIN fireinfo_processors ON fireinfo_profiles_processors.processor_id = fireinfo_processors.id \
1688 WHERE NOT fireinfo_profiles_processors.processor_id IS NULL GROUP BY vendor ORDER BY count DESC", when)
1689
1690 return ((CPU_VENDORS.get(r.vendor, r.vendor), r.count) for r in res)
1691
1692 def get_cpu_clock_speeds(self, when=None):
1693 res = self.db.get("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
1694 SELECT AVG(fireinfo_profiles_processors.clock_speed) AS avg, \
1695 STDDEV(fireinfo_profiles_processors.clock_speed) AS stddev, \
1696 MIN(fireinfo_profiles_processors.clock_speed) AS min, \
1697 MAX(fireinfo_profiles_processors.clock_speed) AS max FROM profiles \
1698 LEFT JOIN fireinfo_profiles_processors ON profiles.id = fireinfo_profiles_processors.profile_id \
1699 WHERE NOT fireinfo_profiles_processors.processor_id IS NULL \
1700 AND fireinfo_profiles_processors.clock_speed > 0", when)
1701
1702 if res:
1703 return (res.avg or 0, res.stddev or 0, res.min or 0, res.max or 0)
1704
1705 def get_cpus_with_platform_and_flag(self, platform, flag, when=None):
1706 res = self.db.get("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id), \
1707 processors AS (SELECT fireinfo_processors.id AS id, fireinfo_processors.flags AS flags FROM profiles \
1708 LEFT JOIN fireinfo_profiles_processors ON profiles.id = fireinfo_profiles_processors.profile_id \
1709 LEFT JOIN fireinfo_processors ON fireinfo_profiles_processors.processor_id = fireinfo_processors.id \
1710 LEFT JOIN fireinfo_profiles_arches ON profiles.id = fireinfo_profiles_arches.profile_id \
1711 LEFT JOIN fireinfo_arches ON fireinfo_profiles_arches.arch_id = fireinfo_arches.id \
1712 WHERE NOT fireinfo_profiles_processors.processor_id IS NULL \
1713 AND fireinfo_arches.platform = %s AND NOT 'hypervisor' = ANY(fireinfo_processors.flags)) \
1714 SELECT (COUNT(*)::float / (SELECT NULLIF(COUNT(*), 0) FROM processors)) AS count FROM processors \
1715 WHERE %s = ANY(processors.flags)", when, platform, flag)
1716
1717 return res.count or 0
1718
1719 def get_common_cpu_flags_by_platform(self, platform, when=None):
1720 if platform == "arm":
1721 flags = (
1722 "lpae", "neon", "thumb", "thumb2", "thumbee", "vfpv3", "vfpv4",
1723 )
1724 elif platform == "x86":
1725 flags = (
1726 "aes", "avx", "avx2", "lm", "mmx", "mmxext", "nx", "pae",
1727 "pni", "popcnt", "sse", "sse2", "rdrand", "ssse3", "sse4a",
1728 "sse4_1", "sse4_2"
1729 )
1730 else:
1731 return
1732
1733 ret = []
1734 for flag in flags:
1735 ret.append((flag, self.get_cpus_with_platform_and_flag(platform, flag, when=when)))
1736
1737 # Add virtual CPU flag "virt" for virtualization support
1738 if platform == "x86":
1739 ret.append(("virt",
1740 self.get_cpus_with_platform_and_flag(platform, "vmx", when=when) + \
1741 self.get_cpus_with_platform_and_flag(platform, "svm", when=when)))
1742
1743 return sorted(ret, key=lambda x: x[1], reverse=True)
1744
1745 def get_common_memory_amounts(self, when=None):
1746 amounts = (128, 256, 512, 1024, 2 * 1024, 4 * 1024, 8 * 1024, 16 * 1024, 32 * 1024, 64 * 1024)
1747
1748 ret = []
1749 for amount in amounts:
1750 ret.append((amount, self.get_memory_amount(amount * 1024 * 0.95, when=when)))
1751
1752 return ret
1753
1754 def get_memory_amount(self, greather_than, when=None):
1755 res = self.db.get("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
1756 SELECT COUNT(*)::float / (SELECT COUNT(*) FROM profiles \
1757 LEFT JOIN fireinfo_profiles_memory ON profiles.id = fireinfo_profiles_memory.profile_id \
1758 WHERE NOT fireinfo_profiles_memory.amount IS NULL) AS percentage FROM profiles \
1759 LEFT JOIN fireinfo_profiles_memory ON profiles.id = fireinfo_profiles_memory.profile_id \
1760 WHERE fireinfo_profiles_memory.amount >= %s", when, greather_than)
1761
1762 if res:
1763 return res.percentage
1764
1765 def get_memory_amounts(self, when=None):
1766 res = self.db.get("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
1767 SELECT AVG(fireinfo_profiles_memory.amount) AS avg, \
1768 STDDEV(fireinfo_profiles_memory.amount) AS stddev, \
1769 MIN(fireinfo_profiles_memory.amount) AS min, \
1770 MAX(fireinfo_profiles_memory.amount) AS max FROM profiles \
1771 LEFT JOIN fireinfo_profiles_memory ON profiles.id = fireinfo_profiles_memory.profile_id", when)
1772
1773 if res:
1774 return (res.avg or 0, res.stddev or 0, res.min or 0, res.max or 0)
1775
1776 def get_arch_map(self, when=None):
1777 res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
1778 SELECT fireinfo_arches.name AS arch, COUNT(*)::float / (SELECT COUNT(*) FROM profiles) AS count \
1779 FROM profiles \
1780 LEFT JOIN fireinfo_profiles_arches ON profiles.id = fireinfo_profiles_arches.profile_id \
1781 LEFT JOIN fireinfo_arches ON fireinfo_profiles_arches.arch_id = fireinfo_arches.id \
1782 WHERE NOT fireinfo_profiles_arches.profile_id IS NULL \
1783 GROUP BY fireinfo_arches.id ORDER BY count DESC", when)
1784
1785 return ((r.arch, r.count) for r in res)
1786
1787 # Virtual
1788
1789 def get_hypervisor_map(self, when=None):
1790 res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id), \
1791 virtual_profiles AS (SELECT profiles.id AS profile_id, fireinfo_profiles_virtual.hypervisor_id FROM profiles \
1792 LEFT JOIN fireinfo_profiles_virtual ON profiles.id = fireinfo_profiles_virtual.profile_id \
1793 WHERE fireinfo_profiles_virtual.profile_id IS NOT NULL) \
1794 SELECT COALESCE(fireinfo_hypervisors.name, %s) AS name, \
1795 COUNT(*)::float / (SELECT COUNT(*) FROM virtual_profiles) AS count FROM virtual_profiles \
1796 LEFT JOIN fireinfo_hypervisors ON virtual_profiles.hypervisor_id = fireinfo_hypervisors.id \
1797 GROUP BY fireinfo_hypervisors.name ORDER BY count DESC", when, "unknown")
1798
1799 return ((r.name, r.count) for r in res)
1800
1801 def get_virtual_ratio(self, when=None):
1802 res = self.db.get("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
1803 SELECT COUNT(*)::float / (SELECT COUNT(*) FROM profiles) AS count FROM profiles \
1804 LEFT JOIN fireinfo_profiles_virtual ON profiles.id = fireinfo_profiles_virtual.profile_id \
1805 WHERE fireinfo_profiles_virtual.profile_id IS NOT NULL", when)
1806
1807 if res:
1808 return res.count
1809
1810 # Releases
1811
1812 def get_releases_map(self, when=None):
1813 res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
1814 SELECT fireinfo_releases.name, COUNT(*)::float / (SELECT COUNT(*) FROM profiles) AS count FROM profiles \
1815 LEFT JOIN fireinfo_profiles_releases ON profiles.id = fireinfo_profiles_releases.profile_id \
1816 LEFT JOIN fireinfo_releases ON fireinfo_profiles_releases.release_id = fireinfo_releases.id \
1817 GROUP BY fireinfo_releases.name ORDER BY count DESC", when)
1818
1819 return ((r.name, r.count) for r in res)
1820
1821 def get_kernels_map(self, when=None):
1822 res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
1823 SELECT fireinfo_kernels.name, COUNT(*)::float / (SELECT COUNT(*) FROM profiles) AS count FROM profiles \
1824 LEFT JOIN fireinfo_profiles_kernels ON profiles.id = fireinfo_profiles_kernels.profile_id \
1825 LEFT JOIN fireinfo_kernels ON fireinfo_profiles_kernels.kernel_id = fireinfo_kernels.id \
1826 GROUP BY fireinfo_kernels.name ORDER BY count DESC", when)
1827
1828 return ((r.name, r.count) for r in res)
1829
1830 def _process_devices(self, devices):
1831 result = []
1832
1833 for dev in devices:
1834 dev = Device(self.backend, dev.get("id", None), dev)
1835 result.append(dev)
1836
1837 return result
1838
1839 def get_driver_map(self, driver, when=None):
1840 res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id), \
1841 devices AS (SELECT * FROM profiles \
1842 LEFT JOIN fireinfo_profiles_devices ON profiles.id = fireinfo_profiles_devices.profile_id \
1843 LEFT JOIN fireinfo_devices ON fireinfo_profiles_devices.device_id = fireinfo_devices.id \
1844 WHERE driver = %s) \
1845 SELECT subsystem, model, vendor, driver, deviceclass, \
1846 COUNT(*)::float / (SELECT COUNT(*) FROM devices) AS percentage FROM devices \
1847 GROUP BY subsystem, model, vendor, driver, deviceclass \
1848 ORDER BY percentage DESC", when, driver)
1849
1850 return self._process_devices(res)
1851
1852 subsystem2class = {
1853 "pci" : hwdata.PCI(),
1854 "usb" : hwdata.USB(),
1855 }
1856
1857 def get_vendor_string(self, subsystem, vendor_id):
1858 try:
1859 cls = self.subsystem2class[subsystem]
1860 except KeyError:
1861 return
1862
1863 return cls.get_vendor(vendor_id)
1864
1865 def get_model_string(self, subsystem, vendor_id, model_id):
1866 try:
1867 cls = self.subsystem2class[subsystem]
1868 except KeyError:
1869 return
1870
1871 return cls.get_device(vendor_id, model_id)
1872
1873 def get_vendor_list(self, when=None):
1874 res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
1875 SELECT DISTINCT fireinfo_devices.subsystem AS subsystem, fireinfo_devices.vendor AS vendor FROM profiles \
1876 LEFT JOIN fireinfo_profiles_devices ON profiles.id = fireinfo_profiles_devices.profile_id \
1877 LEFT JOIN fireinfo_devices ON fireinfo_profiles_devices.device_id = fireinfo_devices.id \
1878 WHERE NOT fireinfo_devices.driver = ANY(%s)", when, IGNORED_DEVICES)
1879
1880 vendors = {}
1881 for row in res:
1882 vendor = self.get_vendor_string(row.subsystem, row.vendor)
1883
1884 # Drop if vendor could not be determined
1885 if vendor is None:
1886 continue
1887
1888 try:
1889 vendors[vendor].append((row.subsystem, row.vendor))
1890 except KeyError:
1891 vendors[vendor] = [(row.subsystem, row.vendor)]
1892
1893 vendors = vendors.items()
1894 return sorted(vendors)
1895
1896 def get_devices_by_vendor(self, subsystem, vendor, when=None):
1897 res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id), \
1898 devices AS (SELECT * FROM profiles \
1899 LEFT JOIN fireinfo_profiles_devices ON profiles.id = fireinfo_profiles_devices.profile_id \
1900 LEFT JOIN fireinfo_devices ON fireinfo_profiles_devices.device_id = fireinfo_devices.id \
1901 WHERE NOT fireinfo_devices.driver = ANY(%s)), \
1902 vendor_devices AS (SELECT * FROM devices WHERE devices.subsystem = %s AND devices.vendor = %s) \
1903 SELECT subsystem, model, vendor, driver, deviceclass FROM vendor_devices \
1904 GROUP BY subsystem, model, vendor, driver, deviceclass", when, IGNORED_DEVICES, subsystem, vendor)
1905
1906 return self._process_devices(res)
1907
1908 def get_device_percentage(self, subsystem, vendor, model, when=None):
1909 res = self.db.get("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id), \
1910 devices AS (SELECT * FROM profiles \
1911 LEFT JOIN fireinfo_profiles_devices ON profiles.id = fireinfo_profiles_devices.profile_id \
1912 LEFT JOIN fireinfo_devices ON fireinfo_profiles_devices.device_id = fireinfo_devices.id) \
1913 SELECT COUNT(*)::float / (SELECT COUNT(*) FROM devices) AS percentage FROM devices \
1914 WHERE devices.subsystem = %s AND devices.vendor = %s AND devices.model = %s",
1915 when, subsystem, vendor, model)
1916
1917 if res:
1918 return res.percentage
1919
1920 def get_device_in_profile(self, subsystem, vendor, model, limit=10, when=None):
1921 res = self.db.query("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id), \
1922 profiles_with_device AS (SELECT DISTINCT fireinfo_profiles.public_id FROM profiles \
1923 LEFT JOIN fireinfo_profiles ON profiles.id = fireinfo_profiles.id \
1924 LEFT JOIN fireinfo_profiles_devices ON profiles.id = fireinfo_profiles_devices.profile_id \
1925 LEFT JOIN fireinfo_devices ON fireinfo_profiles_devices.device_id = fireinfo_devices.id \
1926 WHERE fireinfo_devices.subsystem = %s AND fireinfo_devices.vendor = %s \
1927 AND fireinfo_devices.model = %s) \
1928 SELECT * FROM profiles_with_device ORDER BY RANDOM() LIMIT %s",
1929 when, subsystem, vendor, model, limit)
1930
1931 return (r.public_id for r in res)
1932
1933 def get_network_zones_map(self, when=None):
1934 res = self.db.get("WITH profiles AS (SELECT fireinfo_profiles_with_data_at(%s) AS id) \
1935 SELECT COUNT(NULLIF(has_red, FALSE))::float / (SELECT COUNT(*) FROM profiles) AS has_red, \
1936 COUNT(NULLIF(has_green, FALSE))::float / (SELECT COUNT(*) FROM profiles) AS has_green, \
1937 COUNT(NULLIF(has_orange, FALSE))::float / (SELECT COUNT(*) FROM profiles) AS has_orange, \
1938 COUNT(NULLIF(has_blue, FALSE))::float / (SELECT COUNT(*) FROM profiles) AS has_blue FROM profiles \
1939 LEFT JOIN fireinfo_profiles_networks ON profiles.id = fireinfo_profiles_networks.profile_id \
1940 WHERE fireinfo_profiles_networks.profile_id IS NOT NULL", when)
1941
1942 return res