]> git.ipfire.org Git - people/ms/pakfire.git/blame - src/pakfire/system.py
Replace bogomips by fibonnacci benchmark
[people/ms/pakfire.git] / src / pakfire / system.py
CommitLineData
8fe16710
MT
1#!/usr/bin/python
2###############################################################################
3# #
4# Pakfire - The IPFire package management system #
5# Copyright (C) 2011 Pakfire development team #
6# #
7# This program is free software: you can redistribute it and/or modify #
8# it under the terms of the GNU General Public License as published by #
9# the Free Software Foundation, either version 3 of the License, or #
10# (at your option) any later version. #
11# #
12# This program is distributed in the hope that it will be useful, #
13# but WITHOUT ANY WARRANTY; without even the implied warranty of #
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15# GNU General Public License for more details. #
16# #
17# You should have received a copy of the GNU General Public License #
18# along with this program. If not, see <http://www.gnu.org/licenses/>. #
19# #
20###############################################################################
21
22from __future__ import division
23
c62d93f1 24import multiprocessing
8fe16710 25import os
c62d93f1 26import socket
d251d8e7 27import tempfile
c62d93f1 28
aa14071d 29import distro
d251d8e7 30import shell
aa14071d 31
7e11c4db
MT
32from . import _pakfire
33
c62d93f1
MT
34from i18n import _
35
36class System(object):
37 """
38 Class that grants access to several information about
39 the system this software is running on.
40 """
41 @property
42 def hostname(self):
a579a34f
MT
43 hn = socket.gethostname()
44
45 # If a host has got no domain part, we add one.
46 if not "." in hn:
47 hn = "%s.localdomain" % hn
48
49 return hn
c62d93f1 50
aa14071d
MT
51 @property
52 def distro(self):
53 if not hasattr(self, "_distro"):
54 self._distro = distro.Distribution()
55
56 return self._distro
57
790a44cc
MT
58 @property
59 def native_arch(self):
60 """
61 Return the native architecture of the host we
62 are running on.
63 """
64 return os.uname()[4]
65
c62d93f1
MT
66 @property
67 def arch(self):
68 """
69 Return the architecture of the host we are running on.
70 """
b0b48ca3 71 if self.supported_arches and not self.native_arch in self.supported_arches:
790a44cc
MT
72 return self.supported_arches[0]
73
74 return self.native_arch
c62d93f1
MT
75
76 @property
77 def supported_arches(self):
78 """
79 Check what architectures can be built on this host.
80 """
81 host_can_build = {
82 # Host arch : Can build these arches.
83
84 # x86
85 "x86_64" : ["x86_64", "i686",],
86 "i686" : ["i686",],
87
88 # ARM
89 "armv5tel" : ["armv5tel",],
90 "armv5tejl" : ["armv5tel",],
f711a054 91 "armv6l" : ["armv5tel",],
790a44cc 92 "armv7l" : ["armv7hl", "armv5tel",],
c62d93f1 93 "armv7hl" : ["armv7hl", "armv5tel",],
53669ed6 94
879bf392 95 "aarch64" : ["aarch64",],
c62d93f1
MT
96 }
97
98 try:
790a44cc 99 return host_can_build[self.native_arch]
c62d93f1
MT
100 except KeyError:
101 return []
102
103 def host_supports_arch(self, arch):
104 """
105 Check if this host can build for the target architecture "arch".
106 """
107 return arch in self.supported_arches
108
109 @property
110 def cpu_count(self):
111 """
112 Count the number of CPU cores.
113 """
114 return multiprocessing.cpu_count()
115
aa14071d
MT
116 def parse_cpuinfo(self):
117 ret = {}
118
c62d93f1
MT
119 with open("/proc/cpuinfo") as f:
120 for line in f.readlines():
c62d93f1 121 try:
aa14071d
MT
122 # Split the lines by colons.
123 a, b = line.split(":")
124
125 # Strip whitespace.
126 a = a.strip()
127 b = b.strip()
128
129 ret[a] = b
c62d93f1 130 except:
aa14071d 131 pass
c62d93f1 132
aa14071d
MT
133 return ret
134
135 @property
136 def cpu_model(self):
137 cpuinfo = self.parse_cpuinfo()
c62d93f1 138
90c9f249
MT
139 ret = cpuinfo.get("model name", None)
140
141 # Some ARM platforms do not provide "model name", so we
142 # try an other way.
143 if ret is None:
c62d93f1
MT
144 try:
145 ret = "%(Hardware)s - %(Processor)s" % cpuinfo
146 except KeyError:
147 pass
c62d93f1
MT
148
149 # Remove too many spaces.
90c9f249
MT
150 if ret:
151 ret = " ".join(ret.split())
c62d93f1
MT
152
153 return ret or _("Could not be determined")
154
155 @property
7e11c4db
MT
156 def cpu_bogomips(self):
157 return _pakfire.performance_index()
aa14071d
MT
158
159 def get_loadavg(self):
160 return os.getloadavg()
161
162 @property
163 def loadavg1(self):
164 return self.get_loadavg()[0]
165
166 @property
167 def loadavg5(self):
168 return self.get_loadavg()[1]
169
170 @property
171 def loadavg15(self):
172 return self.get_loadavg()[2]
173
174 def has_overload(self):
175 """
176 Checks, if the load average is not too high.
177
178 On this is to be decided if a new job is taken.
179 """
180 # If there are more than 2 processes in the process queue per CPU
181 # core we will assume that the system has heavy load and to not request
182 # a new job.
183 return self.loadavg5 >= self.cpu_count * 2
184
185 def parse_meminfo(self):
186 ret = {}
187
c62d93f1 188 with open("/proc/meminfo") as f:
aa14071d
MT
189 for line in f.readlines():
190 try:
191 a, b, c = line.split()
c62d93f1 192
aa14071d
MT
193 a = a.strip()
194 a = a.replace(":", "")
195 b = int(b)
196
197 ret[a] = b * 1024
198 except:
199 pass
200
201 return ret
202
203 @property
204 def memory_total(self):
205 meminfo = self.parse_meminfo()
206
207 return meminfo.get("MemTotal", None)
208
209 # For compatibility
210 memory = memory_total
211
212 @property
213 def memory_free(self):
214 meminfo = self.parse_meminfo()
215
7811590a
MT
216 free = meminfo.get("MemFree", None)
217 if free:
218 buffers = meminfo.get("Buffers")
219 cached = meminfo.get("Cached")
220
221 return free + buffers + cached
aa14071d
MT
222
223 @property
224 def swap_total(self):
225 meminfo = self.parse_meminfo()
226
227 return meminfo.get("SwapTotal", None)
228
229 @property
230 def swap_free(self):
231 meminfo = self.parse_meminfo()
c62d93f1 232
aa14071d 233 return meminfo.get("SwapFree", None)
c62d93f1 234
2ad59b6e
MT
235 def get_mountpoint(self, path):
236 return Mountpoint(path)
237
cae35096
MT
238 @property
239 def parallelism(self):
240 """
241 Calculates how many processes should be run
242 simulatneously when compiling.
243 """
879abfa1 244 # Check how many processes would fit into the
a9a26c41
MT
245 # memory when each process takes up to 192MB.
246 multiplicator = self.memory / (192 * 1024 * 1024)
879abfa1
MT
247 multiplicator = round(multiplicator)
248
cae35096 249 # Count the number of online CPU cores.
f7a632ab
MT
250 cpucount = os.sysconf("SC_NPROCESSORS_CONF") * 2
251 cpucount += 1
cae35096 252
f7a632ab 253 return min(multiplicator, cpucount)
cae35096 254
c62d93f1
MT
255
256# Create an instance of this class to only keep it once in memory.
257system = System()
8fe16710
MT
258
259class Mountpoints(object):
2ad59b6e 260 def __init__(self, root="/"):
8fe16710
MT
261 self._mountpoints = []
262
263 # Scan for all mountpoints on the system.
264 self._scan(root)
265
266 def __iter__(self):
267 return iter(self._mountpoints)
268
269 def _scan(self, root):
270 # Get the real path of root.
271 root = os.path.realpath(root)
272
273 # If root is not equal to /, we are in a chroot and
274 # our root must be a mountpoint to count files.
275 if not root == "/":
2ad59b6e 276 mp = Mountpoint("/", root=root)
8fe16710
MT
277 self._mountpoints.append(mp)
278
279 f = open("/proc/mounts")
280
281 for line in f.readlines():
282 line = line.split()
283
284 # The mountpoint is the second argument.
285 mountpoint = line[1]
286
287 # Skip all mountpoints that are not in our root directory.
288 if not mountpoint.startswith(root):
289 continue
290
291 mountpoint = os.path.relpath(mountpoint, root)
292 if mountpoint == ".":
293 mountpoint = "/"
294 else:
295 mountpoint = os.path.join("/", mountpoint)
296
2ad59b6e 297 mp = Mountpoint(mountpoint, root=root)
8fe16710
MT
298
299 if not mp in self._mountpoints:
300 self._mountpoints.append(mp)
301
302 f.close()
303
304 # Sort all mountpoints for better searching.
305 self._mountpoints.sort()
306
307 def add_pkg(self, pkg):
308 for file in pkg.filelist:
309 self.add(file)
310
311 def rem_pkg(self, pkg):
312 for file in pkg.filelist:
313 self.rem(file)
314
315 def add(self, file):
316 for mp in reversed(self._mountpoints):
317 # Check if the file is located on this mountpoint.
318 if not file.name.startswith(mp.path):
319 continue
320
321 # Add file to this mountpoint.
322 mp.add(file)
323 break
324
325 def rem(self, file):
326 for mp in reversed(self._mountpoints):
327 # Check if the file is located on this mountpoint.
328 if not file.name.startswith(mp.path):
329 continue
330
331 # Remove file from this mountpoint.
332 mp.rem(file)
333 break
334
335
336class Mountpoint(object):
2ad59b6e 337 def __init__(self, path, root="/"):
8fe16710
MT
338 self.path = path
339 self.root = root
340
341 # Cache the statvfs call of the mountpoint.
342 self.__stat = None
343
344 # Save the amount of data that is used or freed.
345 self.disk_usage = 0
346
d251d8e7
MT
347 def __repr__(self):
348 return "<%s %s>" % (self.__class__.__name__, self.fullpath)
349
8fe16710
MT
350 def __cmp__(self, other):
351 return cmp(self.fullpath, other.fullpath)
352
353 @property
354 def fullpath(self):
355 path = self.path
356 while path.startswith("/"):
357 path = path[1:]
358
359 return os.path.join(self.root, path)
360
361 @property
362 def stat(self):
363 if self.__stat is None:
364 # Find the next mountpoint, because we cannot
365 # statvfs any path in the FS.
366 path = os.path.realpath(self.fullpath)
367
368 # Walk to root until we find a mountpoint.
369 while not os.path.ismount(path):
370 path = os.path.dirname(path)
371
372 # See what we can get.
373 self.__stat = os.statvfs(path)
374
375 return self.__stat
376
377 @property
378 def free(self):
379 return self.stat.f_bavail * self.stat.f_bsize
380
381 @property
382 def space_needed(self):
383 if self.disk_usage > 0:
384 return self.disk_usage
385
386 return 0
387
388 @property
389 def space_left(self):
390 return self.free - self.space_needed
391
392 def add(self, file):
393 assert file.name.startswith(self.path)
394
395 # Round filesize to 4k blocks.
396 block_size = 4096
397
398 blocks = file.size // block_size
399 if file.size % block_size:
400 blocks += 1
401
402 self.disk_usage += blocks * block_size
403
404 def rem(self, file):
405 assert file.name.startswith(self.path)
406
cd99871f 407 self.disk_usage -= file.size
c62d93f1 408
d251d8e7
MT
409 def is_readonly(self):
410 """
411 Returns True if the mountpoint is mounted read-only.
412 Otherwise False.
413 """
414 # Using the statvfs output does not really work, so we use
415 # a very naive approach here, were we just try to create a
416 # new file. If that works, it's writable.
417
418 try:
419 handle, path = tempfile.mkstemp(prefix="ro-test-", dir=self.fullpath)
420 except OSError, e:
421 # Read-only file system.
422 if e.errno == 30:
423 return True
424
425 # Raise all other exceptions.
426 raise
427 else:
428 # Close the file and remove it.
429 os.close(handle)
430 os.unlink(path)
431
432 return False
433
434 def remount(self, rorw=None):
435 options = "remount"
436 if rorw in ("ro", "rw"):
437 options = "%s,%s" % (options, rorw)
438
439 try:
440 shellenv = shell.ShellExecuteEnvironment(
441 ["mount", "-o", options, self.fullpath],
442 shell=False,
443 )
444 shellenv.execute()
445 except ShellEnvironmentError, e:
446 raise OSError
447
c62d93f1
MT
448
449if __name__ == "__main__":
450 print "Hostname", system.hostname
451 print "Arch", system.arch
452 print "Supported arches", system.supported_arches
453
454 print "CPU Model", system.cpu_model
455 print "CPU count", system.cpu_count
456 print "Memory", system.memory