]>
Commit | Line | Data |
---|---|---|
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 | ||
22 | from __future__ import division | |
23 | ||
c62d93f1 | 24 | import multiprocessing |
8fe16710 | 25 | import os |
c62d93f1 | 26 | import socket |
d251d8e7 | 27 | import tempfile |
c62d93f1 | 28 | |
aa14071d | 29 | import distro |
d251d8e7 | 30 | import shell |
aa14071d | 31 | |
7e11c4db MT |
32 | from . import _pakfire |
33 | ||
c62d93f1 MT |
34 | from i18n import _ |
35 | ||
36 | class 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. | |
257 | system = System() | |
8fe16710 MT |
258 | |
259 | class 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 | ||
336 | class 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 | |
449 | if __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 |