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