]> git.ipfire.org Git - pakfire.git/blob - python/pakfire/system.py
system: bogomips is spelled differently on ARM.
[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 return meminfo.get("MemFree", None)
217
218 @property
219 def swap_total(self):
220 meminfo = self.parse_meminfo()
221
222 return meminfo.get("SwapTotal", None)
223
224 @property
225 def swap_free(self):
226 meminfo = self.parse_meminfo()
227
228 return meminfo.get("SwapFree", None)
229
230 def get_mountpoint(self, path):
231 return Mountpoint(path)
232
233 @property
234 def parallelism(self):
235 """
236 Calculates how many processes should be run
237 simulatneously when compiling.
238 """
239 # Check how many processes would fit into the
240 # memory when each process takes up to 128MB.
241 multiplicator = self.memory / (128 * 1024 * 1024)
242 multiplicator = round(multiplicator)
243
244 # Count the number of online CPU cores.
245 cpucount = os.sysconf("SC_NPROCESSORS_CONF") * 2
246 cpucount += 1
247
248 return min(multiplicator, cpucount)
249
250
251 # Create an instance of this class to only keep it once in memory.
252 system = System()
253
254 class Mountpoints(object):
255 def __init__(self, root="/"):
256 self._mountpoints = []
257
258 # Scan for all mountpoints on the system.
259 self._scan(root)
260
261 def __iter__(self):
262 return iter(self._mountpoints)
263
264 def _scan(self, root):
265 # Get the real path of root.
266 root = os.path.realpath(root)
267
268 # If root is not equal to /, we are in a chroot and
269 # our root must be a mountpoint to count files.
270 if not root == "/":
271 mp = Mountpoint("/", root=root)
272 self._mountpoints.append(mp)
273
274 f = open("/proc/mounts")
275
276 for line in f.readlines():
277 line = line.split()
278
279 # The mountpoint is the second argument.
280 mountpoint = line[1]
281
282 # Skip all mountpoints that are not in our root directory.
283 if not mountpoint.startswith(root):
284 continue
285
286 mountpoint = os.path.relpath(mountpoint, root)
287 if mountpoint == ".":
288 mountpoint = "/"
289 else:
290 mountpoint = os.path.join("/", mountpoint)
291
292 mp = Mountpoint(mountpoint, root=root)
293
294 if not mp in self._mountpoints:
295 self._mountpoints.append(mp)
296
297 f.close()
298
299 # Sort all mountpoints for better searching.
300 self._mountpoints.sort()
301
302 def add_pkg(self, pkg):
303 for file in pkg.filelist:
304 self.add(file)
305
306 def rem_pkg(self, pkg):
307 for file in pkg.filelist:
308 self.rem(file)
309
310 def add(self, file):
311 for mp in reversed(self._mountpoints):
312 # Check if the file is located on this mountpoint.
313 if not file.name.startswith(mp.path):
314 continue
315
316 # Add file to this mountpoint.
317 mp.add(file)
318 break
319
320 def rem(self, file):
321 for mp in reversed(self._mountpoints):
322 # Check if the file is located on this mountpoint.
323 if not file.name.startswith(mp.path):
324 continue
325
326 # Remove file from this mountpoint.
327 mp.rem(file)
328 break
329
330
331 class Mountpoint(object):
332 def __init__(self, path, root="/"):
333 self.path = path
334 self.root = root
335
336 # Cache the statvfs call of the mountpoint.
337 self.__stat = None
338
339 # Save the amount of data that is used or freed.
340 self.disk_usage = 0
341
342 def __cmp__(self, other):
343 return cmp(self.fullpath, other.fullpath)
344
345 @property
346 def fullpath(self):
347 path = self.path
348 while path.startswith("/"):
349 path = path[1:]
350
351 return os.path.join(self.root, path)
352
353 @property
354 def stat(self):
355 if self.__stat is None:
356 # Find the next mountpoint, because we cannot
357 # statvfs any path in the FS.
358 path = os.path.realpath(self.fullpath)
359
360 # Walk to root until we find a mountpoint.
361 while not os.path.ismount(path):
362 path = os.path.dirname(path)
363
364 # See what we can get.
365 self.__stat = os.statvfs(path)
366
367 return self.__stat
368
369 @property
370 def free(self):
371 return self.stat.f_bavail * self.stat.f_bsize
372
373 @property
374 def space_needed(self):
375 if self.disk_usage > 0:
376 return self.disk_usage
377
378 return 0
379
380 @property
381 def space_left(self):
382 return self.free - self.space_needed
383
384 def add(self, file):
385 assert file.name.startswith(self.path)
386
387 # Round filesize to 4k blocks.
388 block_size = 4096
389
390 blocks = file.size // block_size
391 if file.size % block_size:
392 blocks += 1
393
394 self.disk_usage += blocks * block_size
395
396 def rem(self, file):
397 assert file.name.startswith(self.path)
398
399 self.disk_usage += file.size
400
401
402 if __name__ == "__main__":
403 print "Hostname", system.hostname
404 print "Arch", system.arch
405 print "Supported arches", system.supported_arches
406
407 print "CPU Model", system.cpu_model
408 print "CPU count", system.cpu_count
409 print "Memory", system.memory