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