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