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