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