]>
git.ipfire.org Git - ipfire.org.git/blob - build/build.py
edaf98cf8227206d5c50a4eaa1ff56088df23a92
7 from stat
import ST_MTIME
8 from rhpl
.simpleconfig
import SimpleConfigFile
12 def format_time(seconds
, use_hours
=1):
13 if seconds
is None or seconds
< 0:
14 if use_hours
: return '--:--:--'
17 seconds
= int(seconds
)
18 minutes
= seconds
/ 60
19 seconds
= seconds
% 60
22 minutes
= minutes
% 60
23 return '%02i:%02i:%02i' % (hours
, minutes
, seconds
)
25 return '%02i:%02i' % (minutes
, seconds
)
27 def format_number(number
, SI
=0, space
=' '):
28 """Turn numbers into human-readable metric-like numbers"""
29 symbols
= ['', # (none)
44 max_depth
= len(symbols
) - 1
46 # we want numbers between 0 and thresh, but don't exceed the length
47 # of our list. In that event, the formatting will be screwed up,
48 # but it'll still show the right number.
49 while number
> thresh
and depth
< max_depth
:
51 number
= number
/ step
53 if type(number
) == type(1) or type(number
) == type(1L):
54 # it's an int or a long, which means it didn't get divided,
55 # which means it's already short enough
58 # must use 9.95 for proper sizing. For example, 9.99 will be
59 # rounded to 10.0 with the .1f format string (which is too long)
64 return(format
% (float(number
or 0), space
, symbols
[depth
]))
67 "unknown" : "Dunno what the host is doing at the moment...",
68 "compiling" : "The host is really hard working at the moment...",
69 "error" : "Oops! The host had an error...",
70 "idle" : "The host is idle at the moment...",
74 "CPU_NAME" : "CPU model",
75 "CPU_MIPS" : "Bogomips",
76 "CPU_MHZ" : "CPU MHz",
77 "MEM_SIZE" : "Memory size",
82 print "Content-type: text/html"
86 print '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
87 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
88 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
90 <title>IPFire - Builder</title>
91 <style type="text/css">
92 body { font-family: Verdana; font-size: 9pt; background-color:#f0f0f0; }
93 a:link { color: black; text-decoration: none; }
94 a:visited { color: black; text-decoration: none; }
95 table { font-family: Verdana; font-size: 8pt; border: 1px solid; }
96 td.header { background-color: #a0a0a0; text-align: left; color: white;
98 td.date { text-align: center; }
99 font.installed { color: green; }
100 font.deleted { color: red; }
101 font.update { color: blue; }
102 font.error { color: red; }
103 td.log { background: pink; }
104 td.update { background: darkgrey; }
105 /* td.distcc { background: darkgrey; } */
106 td.online { background: green; color: white; text-align: center; }
107 td.offline { background: red; color: white; text-align: center; }
109 <meta http-equiv="refresh" content="90; URL=%s" />
112 <h1>IPFire Builder</h1>
113 <p>This site is for monitoring the build machines...</p>
114 <p>Made: %s</p>''' % (os
.environ
['SCRIPT_NAME'], time
.ctime())
118 print "\t\t</body>\n</html>"
121 for builder
in builders
:
126 def __init__(self
, uuid
=None):
128 self
.db
= os
.path
.join(DB_DIR
, self
.uuid
)
130 if not os
.path
.isdir(self
.db
):
134 'state' : os
.path
.join(self
.db
, "state"),
135 'distcc' : os
.path
.join(self
.db
, "distcc"),
136 'hostname' : os
.path
.join(self
.db
, "hostname"),
137 'build_durations' : os
.path
.join(self
.db
, "build_durations"),
138 'log' : os
.path
.join(self
.db
, "log"),
139 'profile' : os
.path
.join(self
.db
, "profile"),
142 self
.sys
= SimpleConfigFile()
143 self
.sys
.read(self
.fn
['profile'])
145 self
.state
= self
._state
()
147 def _state(self
, state
="unknown"):
148 """State says what the host is doing at the moment.
150 - unknown - Didn't talk with host for more than two hours.
151 - compiling - Host is working
152 - error - Host had an error
153 - idle - Host is idle
154 - hidden - Host was idle or unknown for more than 12 hours"""
156 if not state
== "unknown":
157 f
= open(self
.fn
['state'], "w")
158 f
.write("%s\n" % state
)
161 if not os
.access(self
.fn
['state'], os
.R_OK
):
164 six_h_ago
= int(time
.time()) - (3600 * 6)
165 twelve_h_ago
= int(time
.time()) - (3600 * 12)
166 mtime
= os
.stat(self
.fn
['state'])[ST_MTIME
]
168 if mtime
> six_h_ago
:
170 f
= open(self
.fn
['state'])
171 state
= f
.readline().rstrip("\n")
175 elif mtime
> twelve_h_ago
:
180 def _build_durations(self
, duration
=None):
181 """This returns a 4x-tupel:
182 First value is best build duration.
183 Second value is average build duration.
184 Third value is worst build duration.
185 Fourth value is the whole build duration."""
189 f
= open(self
.fn
['build_durations'], "a")
190 f
.write("%s\n" % int(duration
))
196 all_build_duration
= 0
198 f
= open(self
.fn
['build_durations'])
200 return (None, None, None, None)
203 duration
= f
.readline().rstrip("\n")
206 durations
.append(int(duration
))
209 for duration
in durations
:
210 if duration
< 3600: continue
211 all_build_duration
+= duration
213 avg
= all_build_duration
/ len(durations
)
215 return (durations
[0], avg
, durations
[-1], all_build_duration
)
217 def _log(self
, log
=[]):
220 f
= open(self
.fn
['log'], "w")
221 f
.write("%s\n" % log
)
226 f
= open(self
.fn
['log'])
234 def _last_update(self
):
235 if not os
.access(self
.fn
['state'], os
.R_OK
):
238 return os
.stat(self
.fn
['state'])[ST_MTIME
]
240 def _profile_get(self
, what
=None):
241 data
= self
.sys
.get(what
)
242 if data
and what
.endswith("_SIZE"):
243 data
= format_number(int(data
))
246 def _profile_set(self
, what
):
248 self
.sys
.write(self
.fn
['profile'])
251 return "<Builder %s>" % self
.uuid
254 if self
.state
== "hidden":
257 self
.hostname
= self
._profile
_get
("HOSTNAME")
258 self
.distcc
= self
._profile
_get
("DISTCC")
259 self
.build_durations
= self
._build
_durations
()
260 self
.last_update
= self
._last
_update
()
262 print "<table width='66%'>"
264 ## give hostname or uuid
266 print "\t\t<td class='header' colspan='3' width='100%'>",
276 print "\t\t<td class='state' colspan='2' width='80%'><b>",
277 print stage2desc
[self
.state
],
279 print "\t\t<td class='state' rowspan='7' width='20%'>",
280 print "<img alt='%s' width='128px' height='128px' src='/images/%s.png' />" % (self
.state
, self
.state
,),
285 for key
in [ "CPU_NAME", "CPU_MHZ", "CPU_MIPS", "MEM_SIZE", ]:
287 print "\t\t<td class='sys' width='60%'><b>",
290 print "\t\t<td class='durations' width='40%'>",
291 print self
._profile
_get
(key
) or "N/A"
296 (min, avg
, max, all
) = self
.build_durations
298 print "\t\t<td class='durations' width='60%'>",
299 print "<b>Build durations</b>",
301 print "\t\t<td class='durations' width='40%'>",
303 print "<b>Average:</b> %s h<br />" % format_time(avg
),
305 print "<b>Minimum:</b> %s h<br />" % format_time(min),
307 print "<b>Maximum:</b> %s h<br />" % format_time(max),
309 print "<b>As a whole:</b> %s h" % format_time(all
),
311 if not avg
and not min and not max and not all
:
319 print "\t\t<td class='distcc' width='60%'>",
320 print "<b>Distcc capable</b>",
322 print "\t\t<td class='distcc' width='40%'>",
323 if self
.distcc
== None or self
.distcc
== "0":
326 print "Yes (port: %s)" \
332 if self
.state
== "error":
334 print "\t\t<td class='log' colspan='3' width='100%'>",
335 for line
in self
._log
():
336 print "%s<br/>" % (line
,)
343 print "\t\t<td class='update' colspan='3' width='100%'>",
344 print "Last update:",
345 print time
.strftime("%Y-%b-%d %I:%M %p", time
.localtime(self
.last_update
)),
347 print format_time(int(time
.time() - self
.last_update
)),
355 form
= cgi
.FieldStorage()
356 action
= form
.getfirst('action')
358 if action
in [ "compiling", "error", "idle", "set" ]:
359 builder
= Builder(form
.getfirst('uuid'))
360 if not action
== "set":
361 builder
._state
(action
)
363 key
= form
.getfirst('key')
364 val
= form
.getfirst('val')
365 if key
== "duration":
366 builder
._build
_durations
(val
)
368 builder
._profile
_set
((key
, val
))
369 elif action
== "error":
370 log
= form
.getfirst('log')
374 for uuid
in os
.listdir(DB_DIR
):
375 if not os
.path
.isdir(os
.path
.join(DB_DIR
, uuid
)): continue
376 builders
.append(Builder(uuid
))