sysinfo.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. # Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
  2. #
  3. # Permission to use, copy, modify, and distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  8. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. '''This module returns system information.'''
  16. import os
  17. import sys
  18. import re
  19. import subprocess
  20. import os.path
  21. import platform
  22. import time
  23. from datetime import timedelta
  24. class SysInfo:
  25. def __init__(self):
  26. self._num_processors = None
  27. self._endianness = 'Unknown'
  28. self._hostname = ''
  29. self._platform_name = 'Unknown'
  30. self._platform_version = 'Unknown'
  31. self._platform_machine = 'Unknown'
  32. self._platform_is_smp = None
  33. self._uptime = None
  34. self._loadavg = None
  35. self._mem_total = None
  36. self._mem_free = None
  37. self._mem_swap_total = None
  38. self._mem_swap_free = None
  39. self._net_interfaces = 'Unknown\n'
  40. self._net_routing_table = 'Unknown\n'
  41. self._net_stats = 'Unknown\n'
  42. self._net_connections = 'Unknown\n'
  43. # The following are Linux speicific, and should eventually be removed
  44. # from this level; for now we simply default to None (so they won't
  45. # be printed)
  46. self._platform_distro = None
  47. self._mem_cached = None
  48. self._mem_buffers = None
  49. def get_num_processors(self):
  50. """Returns the number of processors. This is the number of
  51. hyperthreads when hyper-threading is enabled.
  52. """
  53. return self._num_processors
  54. def get_endianness(self):
  55. """Returns 'big' or 'little'."""
  56. return self._endianness
  57. def get_platform_hostname(self):
  58. """Returns the hostname of the system."""
  59. return self._hostname
  60. def get_platform_name(self):
  61. """Returns the platform name (uname -s)."""
  62. return self._platform_name
  63. def get_platform_version(self):
  64. """Returns the platform version (uname -v)."""
  65. return self._platform_version
  66. def get_platform_machine(self):
  67. """Returns the platform machine architecture."""
  68. return self._platform_machine
  69. def get_platform_is_smp(self):
  70. """Returns True if an SMP kernel is being used, False otherwise."""
  71. return self._platform_is_smp
  72. def get_platform_distro(self):
  73. """Returns the name of the OS distribution in use.
  74. Note: the concept of 'distribution' is Linux specific. This shouldn't
  75. be at this level.
  76. """
  77. return self._platform_distro
  78. def get_uptime(self):
  79. """Returns the uptime in seconds."""
  80. return self._uptime
  81. def get_uptime_desc(self):
  82. """Returns the uptime in human readable form.
  83. Specifically, the format is '[DD day[s],] hh:mm'.
  84. It returns None if _uptime is None.
  85. """
  86. if self._uptime is None:
  87. return None
  88. uptime_desc = ''
  89. time_delta = timedelta(seconds=self._uptime)
  90. days = time_delta.days
  91. if days > 0:
  92. uptime_desc += ('%d day%s, ' % (days, 's' if days > 1 else ''))
  93. hours = int(time_delta.seconds / 3600)
  94. minutes = int((time_delta.seconds - hours * 3600) / 60)
  95. uptime_desc += ('%d:%02d' % (hours, minutes))
  96. return uptime_desc
  97. def get_loadavg(self):
  98. """Returns the load average as 3 floating point values in an array."""
  99. return self._loadavg
  100. def get_mem_total(self):
  101. """Returns the total amount of memory in bytes."""
  102. return self._mem_total
  103. def get_mem_free(self):
  104. """Returns the amount of free memory in bytes."""
  105. return self._mem_free
  106. def get_mem_cached(self):
  107. """Returns the amount of cached memory in bytes."""
  108. return self._mem_cached
  109. def get_mem_buffers(self):
  110. """Returns the amount of buffer in bytes."""
  111. return self._mem_buffers
  112. def get_mem_swap_total(self):
  113. """Returns the total amount of swap in bytes."""
  114. return self._mem_swap_total
  115. def get_mem_swap_free(self):
  116. """Returns the amount of free swap in bytes."""
  117. return self._mem_swap_free
  118. def get_net_interfaces(self):
  119. """Returns information about network interfaces (as a multi-line string)."""
  120. return self._net_interfaces
  121. def get_net_routing_table(self):
  122. """Returns information about network routing table (as a multi-line string)."""
  123. return self._net_routing_table
  124. def get_net_stats(self):
  125. """Returns network statistics (as a multi-line string)."""
  126. return self._net_stats
  127. def get_net_connections(self):
  128. """Returns network connection information (as a multi-line string)."""
  129. return self._net_connections
  130. class SysInfoPOSIX(SysInfo):
  131. """Common POSIX implementation of the SysInfo class.
  132. See the SysInfo class documentation for more information.
  133. """
  134. def __init__(self):
  135. super().__init__()
  136. self._num_processors = os.sysconf('SC_NPROCESSORS_CONF')
  137. self._endianness = sys.byteorder
  138. u = os.uname()
  139. self._platform_name = u[0]
  140. self._platform_version = u[2]
  141. self._platform_machine = u[4]
  142. class SysInfoLinux(SysInfoPOSIX):
  143. """Linux implementation of the SysInfo class.
  144. See the SysInfo class documentation for more information.
  145. """
  146. def __init__(self):
  147. super().__init__()
  148. with open('/proc/sys/kernel/hostname') as f:
  149. self._hostname = f.read().strip()
  150. with open('/proc/version') as f:
  151. self._platform_is_smp = ' SMP ' in f.read().strip()
  152. with open('/proc/uptime') as f:
  153. u = f.read().strip().split(' ')
  154. if len(u) > 1:
  155. self._uptime = int(round(float(u[0])))
  156. with open('/proc/loadavg') as f:
  157. l = f.read().strip().split(' ')
  158. if len(l) >= 3:
  159. self._loadavg = (float(l[0]), float(l[1]), float(l[2]))
  160. with open('/proc/meminfo') as f:
  161. m = f.readlines()
  162. for line in m:
  163. r = re.match('^MemTotal:\s+(.*)\s*kB', line)
  164. if r:
  165. self._mem_total = int(r.group(1).strip()) * 1024
  166. continue
  167. r = re.match('^MemFree:\s+(.*)\s*kB', line)
  168. if r:
  169. self._mem_free = int(r.group(1).strip()) * 1024
  170. continue
  171. r = re.match('^Cached:\s+(.*)\s*kB', line)
  172. if r:
  173. self._mem_cached = int(r.group(1).strip()) * 1024
  174. continue
  175. r = re.match('^Buffers:\s+(.*)\s*kB', line)
  176. if r:
  177. self._mem_buffers = int(r.group(1).strip()) * 1024
  178. continue
  179. r = re.match('^SwapTotal:\s+(.*)\s*kB', line)
  180. if r:
  181. self._mem_swap_total = int(r.group(1).strip()) * 1024
  182. continue
  183. r = re.match('^SwapFree:\s+(.*)\s*kB', line)
  184. if r:
  185. self._mem_swap_free = int(r.group(1).strip()) * 1024
  186. continue
  187. self._platform_distro = None
  188. try:
  189. s = subprocess.check_output(['lsb_release', '-a'])
  190. for line in s.decode('utf-8').split('\n'):
  191. r = re.match('^Description:(.*)', line)
  192. if r:
  193. self._platform_distro = r.group(1).strip()
  194. break
  195. except (subprocess.CalledProcessError, OSError):
  196. pass
  197. if self._platform_distro is None:
  198. files = ['/etc/debian_release',
  199. '/etc/debian_version',
  200. '/etc/SuSE-release',
  201. '/etc/UnitedLinux-release',
  202. '/etc/mandrake-release',
  203. '/etc/gentoo-release',
  204. '/etc/fedora-release',
  205. '/etc/redhat-release',
  206. '/etc/redhat_version',
  207. '/etc/slackware-release',
  208. '/etc/slackware-version',
  209. '/etc/arch-release',
  210. '/etc/lsb-release',
  211. '/etc/mageia-release']
  212. for fn in files:
  213. if os.path.exists(fn):
  214. with open(fn) as f:
  215. self._platform_distro = f.read().strip()
  216. break
  217. if self._platform_distro is None:
  218. self._platform_distro = 'Unknown'
  219. try:
  220. s = subprocess.check_output(['ip', 'addr'])
  221. self._net_interfaces = s.decode('utf-8')
  222. except (subprocess.CalledProcessError, OSError):
  223. self._net_interfaces = 'Warning: "ip addr" command failed.\n'
  224. try:
  225. s = subprocess.check_output(['ip', 'route'])
  226. self._net_routing_table = s.decode('utf-8')
  227. self._net_routing_table += '\n'
  228. s = subprocess.check_output(['ip', '-f', 'inet6', 'route'])
  229. self._net_routing_table += s.decode('utf-8')
  230. except (subprocess.CalledProcessError, OSError):
  231. self._net_routing_table = 'Warning: "ip route" or "ip -f inet6 route" command failed.\n'
  232. try:
  233. s = subprocess.check_output(['netstat', '-s'])
  234. self._net_stats = s.decode('utf-8')
  235. except (subprocess.CalledProcessError, OSError):
  236. self._net_stats = 'Warning: "netstat -s" command failed.\n'
  237. try:
  238. s = subprocess.check_output(['netstat', '-apn'])
  239. self._net_connections = s.decode('utf-8')
  240. except (subprocess.CalledProcessError, OSError):
  241. self._net_connections = 'Warning: "netstat -apn" command failed.\n'
  242. class SysInfoBSD(SysInfoPOSIX):
  243. """Common BSD implementation of the SysInfo class.
  244. See the SysInfo class documentation for more information.
  245. """
  246. def __init__(self):
  247. super().__init__()
  248. try:
  249. s = subprocess.check_output(['hostname'])
  250. self._hostname = s.decode('utf-8').strip()
  251. except (subprocess.CalledProcessError, OSError):
  252. pass
  253. try:
  254. s = subprocess.check_output(['sysctl', '-n', 'hw.physmem'])
  255. self._mem_total = int(s.decode('utf-8').strip())
  256. except (subprocess.CalledProcessError, OSError):
  257. pass
  258. try:
  259. s = subprocess.check_output(['ifconfig'])
  260. self._net_interfaces = s.decode('utf-8')
  261. except (subprocess.CalledProcessError, OSError):
  262. self._net_interfaces = 'Warning: "ifconfig" command failed.\n'
  263. try:
  264. s = subprocess.check_output(['netstat', '-s'])
  265. self._net_stats = s.decode('utf-8')
  266. except (subprocess.CalledProcessError, OSError):
  267. self._net_stats = 'Warning: "netstat -s" command failed.\n'
  268. try:
  269. s = subprocess.check_output(['netstat', '-an'])
  270. self._net_connections = s.decode('utf-8')
  271. except (subprocess.CalledProcessError, OSError):
  272. self._net_connections = 'Warning: "netstat -an" command failed.\n'
  273. try:
  274. s = subprocess.check_output(['netstat', '-nr'])
  275. self._net_routing_table = s.decode('utf-8')
  276. except (subprocess.CalledProcessError, OSError):
  277. self._net_connections = 'Warning: "netstat -nr" command failed.\n'
  278. class SysInfoOpenBSD(SysInfoBSD):
  279. """OpenBSD implementation of the SysInfo class.
  280. See the SysInfo class documentation for more information.
  281. """
  282. def __init__(self):
  283. super().__init__()
  284. try:
  285. s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
  286. t = s.decode('utf-8').strip()
  287. sec = time.time() - int(t)
  288. self._uptime = int(round(sec))
  289. except (subprocess.CalledProcessError, OSError):
  290. pass
  291. try:
  292. s = subprocess.check_output(['sysctl', '-n', 'vm.loadavg'])
  293. l = s.decode('utf-8').strip().split(' ')
  294. if len(l) >= 3:
  295. self._loadavg = (float(l[0]), float(l[1]), float(l[2]))
  296. except (subprocess.CalledProcessError, OSError):
  297. pass
  298. try:
  299. s = subprocess.check_output(['vmstat'])
  300. lines = s.decode('utf-8').split('\n')
  301. v = re.split('\s+', lines[2])
  302. used = int(v[4]) * 1024
  303. self._mem_free = self._mem_total - used
  304. except (subprocess.CalledProcessError, OSError):
  305. pass
  306. try:
  307. s = subprocess.check_output(['swapctl', '-s', '-k'])
  308. l = s.decode('utf-8').strip()
  309. r = re.match('^total: (\d+) 1K-blocks allocated, (\d+) used, (\d+) available', l)
  310. if r:
  311. self._mem_swap_total = int(r.group(1).strip()) * 1024
  312. self._mem_swap_free = int(r.group(3).strip()) * 1024
  313. except (subprocess.CalledProcessError, OSError):
  314. pass
  315. class SysInfoFreeBSDOSX(SysInfoBSD):
  316. """Shared code for the FreeBSD and OS X implementations of the SysInfo
  317. class. See the SysInfo class documentation for more information.
  318. """
  319. def __init__(self):
  320. super().__init__()
  321. try:
  322. s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
  323. t = s.decode('utf-8').strip()
  324. r = re.match('^\{\s+sec\s+\=\s+(\d+),.*', t)
  325. if r:
  326. sec = time.time() - int(r.group(1))
  327. self._uptime = int(round(sec))
  328. except (subprocess.CalledProcessError, OSError):
  329. pass
  330. try:
  331. s = subprocess.check_output(['sysctl', '-n', 'vm.loadavg'])
  332. l = s.decode('utf-8').strip()
  333. r = re.match('^\{(.*)\}$', l)
  334. if r:
  335. la = r.group(1).strip().split(' ')
  336. else:
  337. la = l.split(' ')
  338. if len(la) >= 3:
  339. self._loadavg = (float(la[0]), float(la[1]), float(la[2]))
  340. except (subprocess.CalledProcessError, OSError):
  341. pass
  342. class SysInfoFreeBSD(SysInfoFreeBSDOSX):
  343. """FreeBSD implementation of the SysInfo class.
  344. See the SysInfo class documentation for more information.
  345. """
  346. def __init__(self):
  347. super().__init__()
  348. try:
  349. # There doesn't seem to be an easy way to reliably detect whether
  350. # the kernel was built with SMP support on FreeBSD. We use
  351. # a sysctl variable that is only defined in SMP kernels.
  352. # This assumption seems to hold for recent several versions of
  353. # FreeBSD, but it may not always be so for future versions.
  354. s = subprocess.check_output(['sysctl', '-n',
  355. 'kern.smp.forward_signal_enabled'])
  356. self._platform_is_smp = True # the value doesn't matter
  357. except subprocess.CalledProcessError:
  358. # if this variable isn't defined we should see this exception.
  359. # intepret it as an indication of non-SMP kernel.
  360. self._platform_is_smp = False
  361. except OSError:
  362. pass
  363. try:
  364. s = subprocess.check_output(['vmstat', '-H'])
  365. lines = s.decode('utf-8').split('\n')
  366. v = re.split('\s+', lines[2])
  367. used = int(v[4]) * 1024
  368. self._mem_free = self._mem_total - used
  369. except (subprocess.CalledProcessError, OSError):
  370. pass
  371. try:
  372. s = subprocess.check_output(['swapctl', '-s', '-k'])
  373. l = s.decode('utf-8').strip()
  374. r = re.match('^Total:\s+(\d+)\s+(\d+)', l)
  375. if r:
  376. self._mem_swap_total = int(r.group(1).strip()) * 1024
  377. self._mem_swap_free = self._mem_swap_total - (int(r.group(2).strip()) * 1024)
  378. except (subprocess.CalledProcessError, OSError):
  379. pass
  380. class SysInfoOSX(SysInfoFreeBSDOSX):
  381. """OS X (Darwin) implementation of the SysInfo class.
  382. See the SysInfo class documentation for more information.
  383. """
  384. def __init__(self):
  385. super().__init__()
  386. # note; this call overrides the value already set when hw.physmem
  387. # was read. However, on OSX, physmem is not necessarily the correct
  388. # value. But since it does not fail and does work on most BSD's, it's
  389. # left in the base class and overwritten here
  390. self._mem_total = None
  391. try:
  392. s = subprocess.check_output(['sysctl', '-n', 'hw.memsize'])
  393. self._mem_total = int(s.decode('utf-8').strip())
  394. except (subprocess.CalledProcessError, OSError):
  395. pass
  396. try:
  397. s = subprocess.check_output(['vm_stat'])
  398. lines = s.decode('utf-8').split('\n')
  399. # store all values in a dict
  400. values = {}
  401. page_size = None
  402. page_size_re = re.compile('.*page size of ([0-9]+) bytes')
  403. for line in lines:
  404. page_size_m = page_size_re.match(line)
  405. if page_size_m:
  406. page_size = int(page_size_m.group(1))
  407. else:
  408. key, _, value = line.partition(':')
  409. values[key] = value.strip()[:-1]
  410. # Only calculate memory if page size is known
  411. if page_size is not None:
  412. self._mem_free = int(values['Pages free']) * page_size +\
  413. int(values['Pages speculative']) * page_size
  414. except (subprocess.CalledProcessError, OSError):
  415. pass
  416. try:
  417. s = subprocess.check_output(['sysctl', '-n', 'vm.swapusage'])
  418. l = s.decode('utf-8').strip()
  419. r = re.match('^total = (\d+\.\d+)M\s+used = (\d+\.\d+)M\s+free = (\d+\.\d+)M', l)
  420. if r:
  421. self._mem_swap_total = float(r.group(1).strip()) * 1024
  422. self._mem_swap_free = float(r.group(3).strip()) * 1024
  423. except (subprocess.CalledProcessError, OSError):
  424. pass
  425. class SysInfoTestcase(SysInfo):
  426. def __init__(self):
  427. super().__init__()
  428. self._endianness = 'bigrastafarian'
  429. self._platform_name = 'b10test'
  430. self._uptime = 131072
  431. def SysInfoFromFactory():
  432. osname = platform.system()
  433. if osname == 'Linux':
  434. return SysInfoLinux()
  435. elif osname == 'OpenBSD':
  436. return SysInfoOpenBSD()
  437. elif osname == 'FreeBSD':
  438. return SysInfoFreeBSD()
  439. elif osname == 'Darwin':
  440. return SysInfoOSX()
  441. elif osname == 'BIND10Testcase':
  442. return SysInfoTestcase()
  443. else:
  444. return SysInfo()