]> git.ipfire.org Git - pakfire.git/blob - python/pakfire/system.py
495ec9273d6ebd58cae55650c4aa5a9ca7892867
[pakfire.git] / python / 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
28 import distro
29
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):
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
46
47 @property
48 def distro(self):
49 if not hasattr(self, "_distro"):
50 self._distro = distro.Distribution()
51
52 return self._distro
53
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
62 @property
63 def arch(self):
64 """
65 Return the architecture of the host we are running on.
66 """
67 if self.supported_arches and not self.native_arch in self.supported_arches:
68 return self.supported_arches[0]
69
70 return self.native_arch
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",],
87 "armv6l" : ["armv5tel",],
88 "armv7l" : ["armv7hl", "armv5tel",],
89 "armv7hl" : ["armv7hl", "armv5tel",],
90 }
91
92 try:
93 return host_can_build[self.native_arch]
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
110 def parse_cpuinfo(self):
111 ret = {}
112
113 with open("/proc/cpuinfo") as f:
114 for line in f.readlines():
115 try:
116 # Split the lines by colons.
117 a, b = line.split(":")
118
119 # Strip whitespace.
120 a = a.strip()
121 b = b.strip()
122
123 ret[a] = b
124 except:
125 pass
126
127 return ret
128
129 @property
130 def cpu_model(self):
131 cpuinfo = self.parse_cpuinfo()
132
133 ret = None
134 if self.arch.startswith("arm"):
135 try:
136 ret = "%(Hardware)s - %(Processor)s" % cpuinfo
137 except KeyError:
138 pass
139 else:
140 ret = cpuinfo.get("model name", None)
141
142 # Remove too many spaces.
143 ret = " ".join(ret.split())
144
145 return ret or _("Could not be determined")
146
147 @property
148 def cpu_bogomips(self):
149 cpuinfo = self.parse_cpuinfo()
150
151 for key in ("bogomips", "BogoMIPS"):
152 bogomips = cpuinfo.get(key, None)
153
154 if bogomips is None:
155 continue
156
157 return float(bogomips) * self.cpu_count
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
188 with open("/proc/meminfo") as f:
189 for line in f.readlines():
190 try:
191 a, b, c = line.split()
192
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
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
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()
232
233 return meminfo.get("SwapFree", None)
234
235 def get_mountpoint(self, path):
236 return Mountpoint(path)
237
238 @property
239 def parallelism(self):
240 """
241 Calculates how many processes should be run
242 simulatneously when compiling.
243 """
244 # Check how many processes would fit into the
245 # memory when each process takes up to 128MB.
246 multiplicator = self.memory / (128 * 1024 * 1024)
247 multiplicator = round(multiplicator)
248
249 # Count the number of online CPU cores.
250 cpucount = os.sysconf("SC_NPROCESSORS_CONF") * 2
251 cpucount += 1
252
253 return min(multiplicator, cpucount)
254
255
256 # Create an instance of this class to only keep it once in memory.
257 system = System()
258
259 class Mountpoints(object):
260 def __init__(self, root="/"):
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 == "/":
276 mp = Mountpoint("/", root=root)
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
297 mp = Mountpoint(mountpoint, root=root)
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):
337 def __init__(self, path, root="/"):
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
347 def __cmp__(self, other):
348 return cmp(self.fullpath, other.fullpath)
349
350 @property
351 def fullpath(self):
352 path = self.path
353 while path.startswith("/"):
354 path = path[1:]
355
356 return os.path.join(self.root, path)
357
358 @property
359 def stat(self):
360 if self.__stat is None:
361 # Find the next mountpoint, because we cannot
362 # statvfs any path in the FS.
363 path = os.path.realpath(self.fullpath)
364
365 # Walk to root until we find a mountpoint.
366 while not os.path.ismount(path):
367 path = os.path.dirname(path)
368
369 # See what we can get.
370 self.__stat = os.statvfs(path)
371
372 return self.__stat
373
374 @property
375 def free(self):
376 return self.stat.f_bavail * self.stat.f_bsize
377
378 @property
379 def space_needed(self):
380 if self.disk_usage > 0:
381 return self.disk_usage
382
383 return 0
384
385 @property
386 def space_left(self):
387 return self.free - self.space_needed
388
389 def add(self, file):
390 assert file.name.startswith(self.path)
391
392 # Round filesize to 4k blocks.
393 block_size = 4096
394
395 blocks = file.size // block_size
396 if file.size % block_size:
397 blocks += 1
398
399 self.disk_usage += blocks * block_size
400
401 def rem(self, file):
402 assert file.name.startswith(self.path)
403
404 self.disk_usage += file.size
405
406
407 if __name__ == "__main__":
408 print "Hostname", system.hostname
409 print "Arch", system.arch
410 print "Supported arches", system.supported_arches
411
412 print "CPU Model", system.cpu_model
413 print "CPU count", system.cpu_count
414 print "Memory", system.memory