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(\"privacy.popups.firstTime\", false);\n")
200 userprefs
.write("user_pref(\"plugin.default_plugin_disabled\", false);\n")
201 userprefs
.write("user_pref(\"dom.max_script_run_time\", 0);\n")
202 userprefs
.write("user_pref(\"dom.max_script_run_time\", 0);\n")
203 userprefs
.write("user_pref(\"dom.allow_scripts_to_close_windows\", true);\n")
206 def perform_initial_registration(this
):
207 dummy_file
= os
.path
.join(this
.tmpdir
, "dummy.html")
208 f
= open(dummy_file
, "w")
209 f
.write("<html><body onload=\"window.close()\" /></html>\n")
211 logging
.info("running dummy firefox under profile, for initial component-registration")
212 subprocess
.Popen ([this
.bin
, "-P", this
.profile
, "file://" + dummy_file
],
213 cwd
=this
.tmpdir
, shell
=False, env
=this
.environ
).wait()
215 def run_normal(this
, url
):
216 ff_proc
= subprocess
.Popen ([this
.bin
, "-P", this
.profile
, url
],
217 cwd
=this
.tmpdir
, shell
=False, env
=this
.environ
)
218 logging
.info("started 'firefox-bin ... %s' process (pid %d)"
219 % (url
, ff_proc
.pid
))
223 def run_valgrind(this
, vg_tool
, url
):
224 vg_proc
= subprocess
.Popen (["valgrind",
226 "--log-file=valgrind-" + vg_tool
+ "-log",
227 this
.bin
, "-P", this
.profile
, url
],
228 cwd
=this
.tmpdir
, shell
=False, env
=this
.environ
)
229 logging
.info("started 'valgrind --tool=%s firefox-bin ... %s' process (pid %d)"
230 % (vg_tool
, url
, vg_proc
.pid
))
235 ########################################################################
236 # homebrew memory monitor until valgrind works properly
237 ########################################################################
242 def __init__(self
, name
):
250 s
= self
.get_sample()
253 self
.max = max(self
.max, s
)
257 self
.samples
= max(self
.samples
, 1)
258 logging
.info("__COUNT_%s_MAX!%d" % (self
.name
.upper(), self
.max))
259 logging
.info("__COUNT_%s_AVG!%d" % (self
.name
.upper(), (self
.sum / self
.samples
)))
260 logging
.info("__COUNT_%s_FINAL!%d" % (self
.name
.upper(), self
.final
))
263 class proc_maps_heap_sampler(sampler
):
265 def __init__(self
, procpath
):
266 sampler
.__init
__(self
, "heap")
267 self
.procpath
= procpath
269 def get_sample(self
):
270 map_entry_rx
= re
.compile("^([0-9a-f]+)-([0-9a-f]+)\s+[-r][-w][-x][-p]\s+(?:\S+\s+){3}\[heap\]$")
271 maps_path
= os
.path
.join(self
.procpath
, "maps")
272 maps_file
= open(maps_path
)
273 for line
in maps_file
.xreadlines():
274 m
= map_entry_rx
.match(line
)
276 (lo
,hi
) = m
.group(1,2)
286 class proc_status_sampler(sampler
):
288 def __init__(self
, procpath
, entry
):
289 sampler
.__init
__(self
, entry
)
290 self
.status_entry_rx
= re
.compile("^Vm%s:\s+(\d+) kB" % entry
)
291 self
.procpath
= procpath
293 def get_sample(self
):
294 status_path
= os
.path
.join(self
.procpath
, "status")
295 status_file
= open(status_path
)
296 for line
in status_file
.xreadlines():
297 m
= self
.status_entry_rx
.match(line
)
300 return int(m
.group(1)) * 1024
306 def wait_collecting_memory_stats(process
):
309 procdir
= os
.path
.join("/proc", str(process
.pid
))
310 samplers
= [ proc_status_sampler(procdir
, "RSS"),
311 proc_status_sampler(procdir
, "Size"),
312 proc_maps_heap_sampler(procdir
) ]
315 while process
.returncode
== None:
328 ########################################################################
330 ########################################################################
334 batchprefix
= "batch-"
335 homedir
= "/home/mozvalgrind"
336 vnc2swfpath
= "%s/pyvnc2swf-0.8.2" % homedir
337 url
= "file://%s/workload.xml" % homedir
338 probe_point
= "nsWindow::SetTitle(*"
340 vgopts
= [ "--memcheck:leak-check=yes",
341 "--memcheck:error-limit=no",
342 ("--memcheck:num-callers=%d" % num_frames
),
343 # "--memcheck:leak-resolution=high",
344 # "--memcheck:show-reachable=yes",
345 "--massif:format=html",
346 ("--massif:depth=%d" % num_frames
),
347 "--massif:instrs=yes",
348 "--callgrind:simulate-cache=yes",
349 "--callgrind:simulate-hwpref=yes",
350 # ("--callgrind:dump-before=%s" % probe_point),
351 "--callgrind:I1=65536,2,64",
352 "--callgrind:D1=65536,2,64",
353 "--callgrind:L2=524288,8,64",
358 ######################################################
360 ######################################################
362 def archive_dir(dir, sums
):
364 tar
= res
+ ".tar.gz"
366 if os
.path
.exists(res
):
369 if os
.path
.exists(tar
):
373 ix
= open(os
.path
.join(res
, "index.html"), "w")
374 ix
.write("<html>\n<body>\n")
375 ix
.write("<h1>run: %s</h1>\n" % dir)
378 ix
.write("<h2>Summary info</h2>\n")
379 ix
.write("<table>\n")
381 ix
.write("<tr><td>%s</td><td>%s</td></tr>\n" % (x
, sums
[x
]))
382 ix
.write("</table>\n")
386 ix
.write("<h2>Primary logs</h2>\n")
387 for log
in glob
.glob(os
.path
.join(dir, "valgrind-*-log*")):
388 (dirname
, basename
) = os
.path
.split(log
)
389 shutil
.copy(log
, os
.path
.join(res
, basename
))
390 ix
.write("<a href=\"%s\">%s</a><br />\n" % (basename
, basename
))
394 ix
.write("<h2>Massif results</h2>\n")
395 ix
.write("<h3>Click graph to see details</h3>\n")
396 for mp
in glob
.glob(os
.path
.join(dir, "massif.*.ps")):
397 (dirname
,basename
) = os
.path
.split(mp
)
398 (base
,ext
) = os
.path
.splitext(basename
)
400 html
= base
+ ".html"
401 subprocess
.call(["convert", "-rotate", "270", mp
, os
.path
.join(res
, png
)])
402 shutil
.copy(os
.path
.join(dir, html
), os
.path
.join(res
, html
))
403 ix
.write("<a href=\"%s\"><img src=\"%s\" /></a><br />\n" % (html
, png
))
406 ix
.write("<h2>Movies</h2>\n")
407 for movie
in ["memcheck", "massif", "callgrind"]:
408 for ext
in [".html", ".swf"]:
410 if os
.path
.exists(os
.path
.join(dir, base
)):
411 shutil
.copy(os
.path
.join(dir, base
), os
.path
.join(res
, base
))
412 if os
.path
.exists(os
.path
.join(res
, movie
+ ".html")):
413 ix
.write("<a href=\"%s\">%s movie</a><br />\n" % (movie
+ ".html", movie
))
416 ix
.write("<h2>Callgrind profiles</h2>\n")
417 for cg
in glob
.glob(os
.path
.join(dir, "callgrind.out.*")):
418 (dir, base
) = os
.path
.split(cg
)
419 shutil
.copy(cg
, os
.path
.join(res
, base
))
420 ix
.write("<a href=\"%s\">%s</a><br />\n" % (base
, base
))
422 ix
.write("</body>\n</html>\n")
425 for i
in glob
.glob(os
.path
.join(res
, "*")):
429 subprocess
.call(["tar", "czvf", tar
, res
])
433 def log_result_summaries(tmpdir
):
436 "IR" : re
.compile("==\d+==\s+I\s+refs:\s+([0-9,]+)"),
437 "ALLOC" : re
.compile("==\d+==\s+malloc/free:\s+[0-9,]+\s+allocs,\s+[0-9,]+\s+frees,\s+([0-9,]+)\s+bytes allocated."),
438 "LEAKED" : re
.compile("==\d+==\s+(?:definitely|indirectly)\s+lost:\s+([0-9,]+)\s+bytes"),
439 "DUBIOUS" : re
.compile("==\d+==\s+possibly\s+lost:\s+([0-9,]+)\s+bytes"),
440 "LIVE" : re
.compile("==\d+==\s+still\s+reachable:\s+([0-9,]+)\s+bytes"),
445 for fname
in glob
.glob("%s/valgrind-*-log*" % tmpdir
):
448 for line
in f
.xreadlines():
451 match
= rx
.search(line
)
453 val
= int(match
.group(1).replace(",", ""))
455 val
= val
+ sums
[pat
]
460 logging
.info("__COUNT_%s!%d" % (pat
, sums
[pat
]))
462 archive_dir(tmpdir
, sums
)
466 ########################################################################
468 ########################################################################
470 if len(sys
.argv
) != 2:
471 print("usage: %s <firefox-bin build dir>" % sys
.argv
[0])
474 logging
.basicConfig(level
=logging
.INFO
,
475 format
='%(asctime)s %(levelname)s %(message)s')
477 logging
.info("running as %s" % getpass
.getuser())
479 logging
.info("killing any residual processes in this group")
480 subprocess
.call(["killall", "-q", "-9", "memcheck", "callgrind", "massif", "valgrind", "firefox-bin"])
483 dirs
=glob
.glob(os
.path
.join(homedir
, batchprefix
+ "*"))
486 for olddir
in dirs
[:-5]:
487 logging
.info("cleaning up old directory %s" % olddir
)
488 shutil
.rmtree(olddir
)
491 timestr
= datetime
.datetime
.now().isoformat().replace(":", "-")
492 tmpdir
= tempfile
.mkdtemp(prefix
=(batchprefix
+ timestr
+ "-"), dir=homedir
)
494 logging
.info("started batch run in dir " + tmpdir
)
499 write_vgopts(tmpdir
, vgopts
)
505 ######################################################
506 # note: runit is supervising a single Xvnc on disp 25
507 # there is no need to run one here as well
508 ######################################################
509 # xvnc_proc = start_xvnc(disp, tmpdir)
510 ######################################################
514 runner
= Firefox_runner(batchprefix
, homedir
, ffdir
, timestr
, tmpdir
, disp
)
516 wait_collecting_memory_stats(runner
.run_normal(url
))
519 # for variant in ["memcheck", "massif", "callgrind"]:
520 # recorder = start_xvnc_recorder(vnc2swfpath, disp, variant, tmpdir)
521 # runner.run_valgrind(variant, url).wait()
522 # runner.clear_cache()
523 # complete_xvnc_recording(vnc2swfpath, recorder, variant, tmpdir)
525 log_result_summaries(tmpdir
)
526 logging
.info("valgrind-firefox processes complete")
529 if recorder
and recorder
.poll() == None:
530 logging
.info("killing recorder process %d" % recorder
.pid
)
531 os
.kill(recorder
.pid
, signal
.SIGKILL
)
532 if xvnc_proc
and xvnc_proc
.poll() == None:
533 logging
.info("killing Xvnc process %d" % xvnc_proc
.pid
)
534 os
.kill(xvnc_proc
.pid
, signal
.SIGKILL
)
536 logging
.info("batch run in dir %s complete" % tmpdir
)