3 # ***** BEGIN LICENSE BLOCK *****
4 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 # The contents of this file are subject to the Mozilla Public License Version
7 # 1.1 (the "License"); you may not use this file except in compliance with
8 # the License. You may obtain a copy of the License at
9 # http://www.mozilla.org/MPL/
11 # Software distributed under the License is distributed on an "AS IS" basis,
12 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 # for the specific language governing rights and limitations under the
16 # The Original Code is standalone Firefox memory test.
18 # The Initial Developer of the Original Code is
19 # Mozilla Corporation.
20 # Portions created by the Initial Developer are Copyright (C) 2006
21 # the Initial Developer. All Rights Reserved.
24 # Graydon Hoare <graydon@mozilla.com> (original author)
26 # Alternatively, the contents of this file may be used under the terms of
27 # either the GNU General Public License Version 2 or later (the "GPL"), or
28 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 # in which case the provisions of the GPL or the LGPL are applicable instead
30 # of those above. If you wish to allow use of your version of this file only
31 # under the terms of either the GPL or the LGPL, and not to allow others to
32 # use your version of this file under the terms of the MPL, indicate your
33 # decision by deleting the provisions above and replace them with the notice
34 # and other provisions required by the GPL or the LGPL. If you do not delete
35 # the provisions above, a recipient may use your version of this file under
36 # the terms of any one of the MPL, the GPL or the LGPL.
38 # ***** END LICENSE BLOCK *****
54 def sighandler(signal
, frame
):
55 print "signal %d, throwing" % signal
58 signal
.signal(signal
.SIGHUP
, sighandler
)
61 ########################################################################
63 def start_xvnc(disp
, tmpdir
):
64 xvnc_stdout
= open(os
.path
.join(tmpdir
, "xvnc.stdout"), mode
="w")
65 xvnc_stderr
= open(os
.path
.join(tmpdir
, "xvnc.stderr"), mode
="w")
66 xvnc_proc
= subprocess
.Popen (["Xvnc",
67 "-fp", "/usr/share/X11/fonts/misc",
69 "-SecurityTypes", "None",
70 "-IdleTimeout", "604800",
79 assert xvnc_proc
.poll() == None
80 logging
.info("started Xvnc server (pid %d) on display :%d"
81 % (xvnc_proc
.pid
, disp
))
84 ########################################################################
86 def large_variant_filename(variant
):
87 return variant
+ "-large.swf"
89 def large_variant_html_filename(variant
):
90 return variant
+ "-large.html"
92 def small_variant_filename(variant
):
93 return variant
+ ".swf"
95 def start_xvnc_recorder(vnc2swfpath
, disp
, variant
, tmpdir
):
96 rec_stdout
= open(os
.path
.join(tmpdir
, "vnc2swf.stdout"), mode
="w")
97 rec_stderr
= open(os
.path
.join(tmpdir
, "vnc2swf.stderr"), mode
="w")
98 rec_proc
= subprocess
.Popen ([os
.path
.join(vnc2swfpath
, "vnc2swf.py"),
100 "-o", large_variant_filename(variant
),
101 "-C", "800x600+0+100",
104 "localhost:%d" % disp
],
110 assert rec_proc
.poll() == None
111 logging
.info("started vnc2swf recorder (pid %d) on display :%d"
112 % (rec_proc
.pid
, disp
))
115 def complete_xvnc_recording(vnc2swfpath
, proc
, variant
, tmpdir
):
116 edit_stdout
= open(os
.path
.join(tmpdir
, "edit.stdout"), mode
="w")
117 edit_stderr
= open(os
.path
.join(tmpdir
, "edit.stderr"), mode
="w")
118 logging
.info("killing vnc2swf recorder (pid %d)" % proc
.pid
)
119 os
.kill(proc
.pid
, signal
.SIGINT
)
121 logging
.info("down-sampling video")
122 subprocess
.Popen([os
.path
.join(vnc2swfpath
, "edit.py"),
124 "-o", small_variant_filename(variant
),
128 large_variant_filename(variant
)],
132 stderr
=edit_stderr
).wait()
133 #os.unlink(large_variant_html_filename(variant))
134 #os.unlink(large_variant_filename(variant))
135 logging
.info("output video is in " + small_variant_filename(variant
))
138 ########################################################################
140 def write_vgopts(tmpdir
, vgopts
):
141 f
= open(os
.path
.join(tmpdir
, ".valgrindrc"), "w")
146 ########################################################################
148 class Firefox_runner
:
149 def __init__(this
, batchprefix
, homedir
, ffdir
, timestr
, tmpdir
, disp
):
151 this
.homedir
= homedir
153 this
.profile
= batchprefix
+ timestr
154 this
.profile_dir
= os
.path
.join(this
.tmpdir
, this
.profile
)
155 this
.bin
= os
.path
.join(this
.ffdir
, "firefox-bin")
157 "PATH" : os
.getenv("PATH"),
158 "HOME" : this
.homedir
,
159 "LD_LIBRARY_PATH" : this
.ffdir
,
160 "MOZ_NO_REMOTE" : "1",
162 "DISPLAY" : (":%d" % disp
),
165 this
.kill_initial_profiles_ini()
166 this
.create_tmp_profile()
167 this
.write_proxy_pac_file()
168 this
.write_user_prefs()
169 this
.perform_initial_registration()
171 def kill_initial_profiles_ini(this
):
172 prof
= os
.path
.join(this
.homedir
, ".mozilla")
173 shutil
.rmtree(prof
, True)
177 def clear_cache(this
):
178 shutil
.rmtree(os
.path
.join(this
.profile_dir
, "Cache"), True)
180 def create_tmp_profile(this
):
181 subprocess
.Popen ([this
.bin
, "-CreateProfile", (this
.profile
+ " " + this
.profile_dir
)],
182 cwd
=this
.tmpdir
, shell
=False, env
=this
.environ
).wait()
184 def write_proxy_pac_file(this
):
185 this
.proxypac
= os
.path
.join(this
.tmpdir
, "proxy.pac")
186 p
= open(this
.proxypac
, "w")
187 p
.write("function FindProxyForURL(url, host) {\n"
188 " if (host == \"localhost\")\n"
189 " return \"DIRECT\";\n"
191 " return \"PROXY localhost\";\n"
195 def write_user_prefs(this
):
196 userprefs
= open(os
.path
.join(this
.profile_dir
, "user.js"), "w")
197 userprefs
.write("user_pref(\"network.proxy.autoconfig_url\", \"file://%s\");\n" % this
.proxypac
)
198 userprefs
.write("user_pref(\"network.proxy.type\", 2);\n")
199 userprefs
.write("user_pref(\"dom.max_script_run_time\", 0);\n")
200 userprefs
.write("user_pref(\"dom.max_script_run_time\", 0);\n")
201 userprefs
.write("user_pref(\"dom.allow_scripts_to_close_windows\", true);\n")
204 def perform_initial_registration(this
):
205 dummy_file
= os
.path
.join(this
.tmpdir
, "dummy.html")
206 f
= open(dummy_file
, "w")
207 f
.write("<html><body onload=\"window.close()\" /></html>\n")
209 logging
.info("running dummy firefox under profile, for initial component-registration")
210 subprocess
.Popen ([this
.bin
, "-P", this
.profile
, "file://" + dummy_file
],
211 cwd
=this
.tmpdir
, shell
=False, env
=this
.environ
).wait()
213 def run_normal(this
, url
):
214 ff_proc
= subprocess
.Popen ([this
.bin
, "-P", this
.profile
, url
],
215 cwd
=this
.tmpdir
, shell
=False, env
=this
.environ
)
216 logging
.info("started 'firefox-bin ... %s' process (pid %d)"
217 % (url
, ff_proc
.pid
))
221 def run_valgrind(this
, vg_tool
, url
):
222 vg_proc
= subprocess
.Popen (["valgrind",
224 "--log-file=valgrind-" + vg_tool
+ "-log",
225 this
.bin
, "-P", this
.profile
, url
],
226 cwd
=this
.tmpdir
, shell
=False, env
=this
.environ
)
227 logging
.info("started 'valgrind --tool=%s firefox-bin ... %s' process (pid %d)"
228 % (vg_tool
, url
, vg_proc
.pid
))
233 ########################################################################
234 # homebrew memory monitor until valgrind works properly
235 ########################################################################
240 def __init__(self
, name
):
248 s
= self
.get_sample()
251 self
.max = max(self
.max, s
)
255 self
.samples
= max(self
.samples
, 1)
256 logging
.info("__COUNT_%s_MAX!%d" % (self
.name
.upper(), self
.max))
257 logging
.info("__COUNT_%s_AVG!%d" % (self
.name
.upper(), (self
.sum / self
.samples
)))
258 logging
.info("__COUNT_%s_FINAL!%d" % (self
.name
.upper(), self
.final
))
261 class proc_maps_heap_sampler(sampler
):
263 def __init__(self
, procpath
):
264 sampler
.__init
__(self
, "heap")
265 self
.procpath
= procpath
267 def get_sample(self
):
268 map_entry_rx
= re
.compile("^([0-9a-f]+)-([0-9a-f]+)\s+[-r][-w][-x][-p]\s+(?:\S+\s+){3}\[heap\]$")
269 maps_path
= os
.path
.join(self
.procpath
, "maps")
270 maps_file
= open(maps_path
)
271 for line
in maps_file
.xreadlines():
272 m
= map_entry_rx
.match(line
)
274 (lo
,hi
) = m
.group(1,2)
284 class proc_status_sampler(sampler
):
286 def __init__(self
, procpath
, entry
):
287 sampler
.__init
__(self
, entry
)
288 self
.status_entry_rx
= re
.compile("^Vm%s:\s+(\d+) kB" % entry
)
289 self
.procpath
= procpath
291 def get_sample(self
):
292 status_path
= os
.path
.join(self
.procpath
, "status")
293 status_file
= open(status_path
)
294 for line
in status_file
.xreadlines():
295 m
= self
.status_entry_rx
.match(line
)
298 return int(m
.group(1)) * 1024
304 def wait_collecting_memory_stats(process
):
307 procdir
= os
.path
.join("/proc", str(process
.pid
))
308 samplers
= [ proc_status_sampler(procdir
, "RSS"),
309 proc_status_sampler(procdir
, "Size"),
310 proc_maps_heap_sampler(procdir
) ]
313 while process
.returncode
== None:
326 ########################################################################
328 ########################################################################
332 batchprefix
= "batch-"
333 homedir
= "/home/mozvalgrind"
334 vnc2swfpath
= "%s/pyvnc2swf-0.8.2" % homedir
335 url
= "file://%s/workload.xml" % homedir
336 probe_point
= "nsWindow::SetTitle(*"
338 vgopts
= [ "--memcheck:leak-check=yes",
339 "--memcheck:error-limit=no",
340 ("--memcheck:num-callers=%d" % num_frames
),
341 # "--memcheck:leak-resolution=high",
342 # "--memcheck:show-reachable=yes",
343 "--massif:format=html",
344 ("--massif:depth=%d" % num_frames
),
345 "--massif:instrs=yes",
346 "--callgrind:simulate-cache=yes",
347 "--callgrind:simulate-hwpref=yes",
348 # ("--callgrind:dump-before=%s" % probe_point),
349 "--callgrind:I1=65536,2,64",
350 "--callgrind:D1=65536,2,64",
351 "--callgrind:L2=524288,8,64",
356 ######################################################
358 ######################################################
360 def archive_dir(dir, sums
):
362 tar
= res
+ ".tar.gz"
364 if os
.path
.exists(res
):
367 if os
.path
.exists(tar
):
371 ix
= open(os
.path
.join(res
, "index.html"), "w")
372 ix
.write("<html>\n<body>\n")
373 ix
.write("<h1>run: %s</h1>\n" % dir)
376 ix
.write("<h2>Summary info</h2>\n")
377 ix
.write("<table>\n")
379 ix
.write("<tr><td>%s</td><td>%s</td></tr>\n" % (x
, sums
[x
]))
380 ix
.write("</table>\n")
384 ix
.write("<h2>Primary logs</h2>\n")
385 for log
in glob
.glob(os
.path
.join(dir, "valgrind-*-log*")):
386 (dirname
, basename
) = os
.path
.split(log
)
387 shutil
.copy(log
, os
.path
.join(res
, basename
))
388 ix
.write("<a href=\"%s\">%s</a><br />\n" % (basename
, basename
))
392 ix
.write("<h2>Massif results</h2>\n")
393 ix
.write("<h3>Click graph to see details</h3>\n")
394 for mp
in glob
.glob(os
.path
.join(dir, "massif.*.ps")):
395 (dirname
,basename
) = os
.path
.split(mp
)
396 (base
,ext
) = os
.path
.splitext(basename
)
398 html
= base
+ ".html"
399 subprocess
.call(["convert", "-rotate", "270", mp
, os
.path
.join(res
, png
)])
400 shutil
.copy(os
.path
.join(dir, html
), os
.path
.join(res
, html
))
401 ix
.write("<a href=\"%s\"><img src=\"%s\" /></a><br />\n" % (html
, png
))
404 ix
.write("<h2>Movies</h2>\n")
405 for movie
in ["memcheck", "massif", "callgrind"]:
406 for ext
in [".html", ".swf"]:
408 if os
.path
.exists(os
.path
.join(dir, base
)):
409 shutil
.copy(os
.path
.join(dir, base
), os
.path
.join(res
, base
))
410 if os
.path
.exists(os
.path
.join(res
, movie
+ ".html")):
411 ix
.write("<a href=\"%s\">%s movie</a><br />\n" % (movie
+ ".html", movie
))
414 ix
.write("<h2>Callgrind profiles</h2>\n")
415 for cg
in glob
.glob(os
.path
.join(dir, "callgrind.out.*")):
416 (dir, base
) = os
.path
.split(cg
)
417 shutil
.copy(cg
, os
.path
.join(res
, base
))
418 ix
.write("<a href=\"%s\">%s</a><br />\n" % (base
, base
))
420 ix
.write("</body>\n</html>\n")
423 for i
in glob
.glob(os
.path
.join(res
, "*")):
427 subprocess
.call(["tar", "czvf", tar
, res
])
431 def log_result_summaries(tmpdir
):
434 "IR" : re
.compile("==\d+==\s+I\s+refs:\s+([0-9,]+)"),
435 "ALLOC" : re
.compile("==\d+==\s+malloc/free:\s+[0-9,]+\s+allocs,\s+[0-9,]+\s+frees,\s+([0-9,]+)\s+bytes allocated."),
436 "LEAKED" : re
.compile("==\d+==\s+(?:definitely|indirectly)\s+lost:\s+([0-9,]+)\s+bytes"),
437 "DUBIOUS" : re
.compile("==\d+==\s+possibly\s+lost:\s+([0-9,]+)\s+bytes"),
438 "LIVE" : re
.compile("==\d+==\s+still\s+reachable:\s+([0-9,]+)\s+bytes"),
443 for fname
in glob
.glob("%s/valgrind-*-log*" % tmpdir
):
446 for line
in f
.xreadlines():
449 match
= rx
.search(line
)
451 val
= int(match
.group(1).replace(",", ""))
453 val
= val
+ sums
[pat
]
458 logging
.info("__COUNT_%s!%d" % (pat
, sums
[pat
]))
460 archive_dir(tmpdir
, sums
)
464 ########################################################################
466 ########################################################################
468 if len(sys
.argv
) != 2:
469 print("usage: %s <firefox-bin build dir>" % sys
.argv
[0])
472 logging
.basicConfig(level
=logging
.INFO
,
473 format
='%(asctime)s %(levelname)s %(message)s')
475 logging
.info("running as %s" % getpass
.getuser())
477 logging
.info("killing any residual processes in this group")
478 subprocess
.call(["killall", "-q", "-9", "memcheck", "callgrind", "massif", "valgrind", "firefox-bin"])
481 dirs
=glob
.glob(os
.path
.join(homedir
, batchprefix
+ "*"))
484 for olddir
in dirs
[:-5]:
485 logging
.info("cleaning up old directory %s" % olddir
)
486 shutil
.rmtree(olddir
)
489 timestr
= datetime
.datetime
.now().isoformat().replace(":", "-")
490 tmpdir
= tempfile
.mkdtemp(prefix
=(batchprefix
+ timestr
+ "-"), dir=homedir
)
492 logging
.info("started batch run in dir " + tmpdir
)
497 write_vgopts(tmpdir
, vgopts
)
503 ######################################################
504 # note: runit is supervising a single Xvnc on disp 25
505 # there is no need to run one here as well
506 ######################################################
507 # xvnc_proc = start_xvnc(disp, tmpdir)
508 ######################################################
512 runner
= Firefox_runner(batchprefix
, homedir
, ffdir
, timestr
, tmpdir
, disp
)
514 wait_collecting_memory_stats(runner
.run_normal(url
))
517 # for variant in ["memcheck", "massif", "callgrind"]:
518 # recorder = start_xvnc_recorder(vnc2swfpath, disp, variant, tmpdir)
519 # runner.run_valgrind(variant, url).wait()
520 # runner.clear_cache()
521 # complete_xvnc_recording(vnc2swfpath, recorder, variant, tmpdir)
523 log_result_summaries(tmpdir
)
524 logging
.info("valgrind-firefox processes complete")
527 if recorder
and recorder
.poll() == None:
528 logging
.info("killing recorder process %d" % recorder
.pid
)
529 os
.kill(recorder
.pid
, signal
.SIGKILL
)
530 if xvnc_proc
and xvnc_proc
.poll() == None:
531 logging
.info("killing Xvnc process %d" % xvnc_proc
.pid
)
532 os
.kill(xvnc_proc
.pid
, signal
.SIGKILL
)
534 logging
.info("batch run in dir %s complete" % tmpdir
)