/testing/tests/memtest.py
Python | 534 lines | 391 code | 81 blank | 62 comment | 24 complexity | 9abfda783a947bea6d1cc73681707ecf MD5 | raw file
Possible License(s): AGPL-1.0, MIT, LGPL-3.0, MPL-2.0-no-copyleft-exception, BSD-3-Clause, GPL-2.0, JSON, Apache-2.0, 0BSD, LGPL-2.1
- #!/usr/bin/python
- #
- # ***** BEGIN LICENSE BLOCK *****
- # Version: MPL 1.1/GPL 2.0/LGPL 2.1
- #
- # The contents of this file are subject to the Mozilla Public License Version
- # 1.1 (the "License"); you may not use this file except in compliance with
- # the License. You may obtain a copy of the License at
- # http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS IS" basis,
- # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- # for the specific language governing rights and limitations under the
- # License.
- #
- # The Original Code is standalone Firefox memory test.
- #
- # The Initial Developer of the Original Code is
- # Mozilla Corporation.
- # Portions created by the Initial Developer are Copyright (C) 2006
- # the Initial Developer. All Rights Reserved.
- #
- # Contributor(s):
- # Graydon Hoare <graydon@mozilla.com> (original author)
- #
- # Alternatively, the contents of this file may be used under the terms of
- # either the GNU General Public License Version 2 or later (the "GPL"), or
- # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- # in which case the provisions of the GPL or the LGPL are applicable instead
- # of those above. If you wish to allow use of your version of this file only
- # under the terms of either the GPL or the LGPL, and not to allow others to
- # use your version of this file under the terms of the MPL, indicate your
- # decision by deleting the provisions above and replace them with the notice
- # and other provisions required by the GPL or the LGPL. If you do not delete
- # the provisions above, a recipient may use your version of this file under
- # the terms of any one of the MPL, the GPL or the LGPL.
- #
- # ***** END LICENSE BLOCK *****
- import subprocess
- import tempfile
- import os
- import re
- import getpass
- import datetime
- import logging
- import time
- import sys
- import signal
- import shutil
- import glob
- def sighandler(signal, frame):
- print "signal %d, throwing" % signal
- raise Exception
- signal.signal(signal.SIGHUP, sighandler)
- ########################################################################
- def start_xvnc(disp, tmpdir):
- xvnc_stdout = open(os.path.join(tmpdir, "xvnc.stdout"), mode="w")
- xvnc_stderr = open(os.path.join(tmpdir, "xvnc.stderr"), mode="w")
- xvnc_proc = subprocess.Popen (["Xvnc",
- "-fp", "/usr/share/X11/fonts/misc",
- "-localhost",
- "-SecurityTypes", "None",
- "-IdleTimeout", "604800",
- "-depth", "24",
- "-ac",
- (":%d" % disp)],
- cwd=tmpdir,
- shell=False,
- stdout=xvnc_stdout,
- stderr=xvnc_stderr)
- time.sleep(3)
- assert xvnc_proc.poll() == None
- logging.info("started Xvnc server (pid %d) on display :%d"
- % (xvnc_proc.pid, disp))
- return xvnc_proc
- ########################################################################
- def large_variant_filename(variant):
- return variant + "-large.swf"
- def large_variant_html_filename(variant):
- return variant + "-large.html"
- def small_variant_filename(variant):
- return variant + ".swf"
- def start_xvnc_recorder(vnc2swfpath, disp, variant, tmpdir):
- rec_stdout = open(os.path.join(tmpdir, "vnc2swf.stdout"), mode="w")
- rec_stderr = open(os.path.join(tmpdir, "vnc2swf.stderr"), mode="w")
- rec_proc = subprocess.Popen ([os.path.join(vnc2swfpath, "vnc2swf.py"),
- "-n",
- "-o", large_variant_filename(variant),
- "-C", "800x600+0+100",
- "-r", "2",
- "-t", "video",
- "localhost:%d" % disp],
- cwd=tmpdir,
- shell=False,
- stdout=rec_stdout,
- stderr=rec_stderr)
- time.sleep(3)
- assert rec_proc.poll() == None
- logging.info("started vnc2swf recorder (pid %d) on display :%d"
- % (rec_proc.pid, disp))
- return rec_proc
- def complete_xvnc_recording(vnc2swfpath, proc, variant, tmpdir):
- edit_stdout = open(os.path.join(tmpdir, "edit.stdout"), mode="w")
- edit_stderr = open(os.path.join(tmpdir, "edit.stderr"), mode="w")
- logging.info("killing vnc2swf recorder (pid %d)" % proc.pid)
- os.kill(proc.pid, signal.SIGINT)
- proc.wait()
- logging.info("down-sampling video")
- subprocess.Popen([os.path.join(vnc2swfpath, "edit.py"),
- "-c",
- "-o", small_variant_filename(variant),
- "-t", "video",
- "-s", "0.5",
- "-r", "32",
- large_variant_filename(variant)],
- cwd=tmpdir,
- shell=False,
- stdout=edit_stdout,
- stderr=edit_stderr).wait()
- #os.unlink(large_variant_html_filename(variant))
- #os.unlink(large_variant_filename(variant))
- logging.info("output video is in " + small_variant_filename(variant))
-
- ########################################################################
- def write_vgopts(tmpdir, vgopts):
- f = open(os.path.join(tmpdir, ".valgrindrc"), "w")
- for i in vgopts:
- f.write(i + "\n")
- f.close()
- ########################################################################
- class Firefox_runner:
- def __init__(this, batchprefix, homedir, ffdir, timestr, tmpdir, disp):
- this.tmpdir = tmpdir
- this.homedir = homedir
- this.ffdir = ffdir
- this.profile = batchprefix + timestr
- this.profile_dir = os.path.join(this.tmpdir, this.profile)
- this.bin = os.path.join(this.ffdir, "firefox-bin")
- this.environ = {
- "PATH" : os.getenv("PATH"),
- "HOME" : this.homedir,
- "LD_LIBRARY_PATH" : this.ffdir,
- "MOZ_NO_REMOTE" : "1",
- # "DISPLAY" : ":0",
- "DISPLAY" : (":%d" % disp),
- }
- this.kill_initial_profiles_ini()
- this.create_tmp_profile()
- this.write_proxy_pac_file()
- this.write_user_prefs()
- this.perform_initial_registration()
- def kill_initial_profiles_ini(this):
- prof = os.path.join(this.homedir, ".mozilla")
- shutil.rmtree(prof, True)
- os.mkdir(prof)
- def clear_cache(this):
- shutil.rmtree(os.path.join(this.profile_dir, "Cache"), True)
- def create_tmp_profile(this):
- subprocess.Popen ([this.bin, "-CreateProfile", (this.profile + " " + this.profile_dir)],
- cwd=this.tmpdir, shell=False, env=this.environ).wait()
- def write_proxy_pac_file(this):
- this.proxypac = os.path.join(this.tmpdir, "proxy.pac")
- p = open(this.proxypac, "w")
- p.write("function FindProxyForURL(url, host) {\n"
- " if (host == \"localhost\")\n"
- " return \"DIRECT\";\n"
- " else\n"
- " return \"PROXY localhost\";\n"
- "}\n")
- p.close()
- def write_user_prefs(this):
- userprefs = open(os.path.join(this.profile_dir, "user.js"), "w")
- userprefs.write("user_pref(\"network.proxy.autoconfig_url\", \"file://%s\");\n" % this.proxypac)
- userprefs.write("user_pref(\"network.proxy.type\", 2);\n")
- userprefs.write("user_pref(\"dom.max_script_run_time\", 0);\n")
- userprefs.write("user_pref(\"hangmonitor.timeout\", 0);\n");
- userprefs.write("user_pref(\"dom.allow_scripts_to_close_windows\", true);\n")
- userprefs.close()
- def perform_initial_registration(this):
- dummy_file = os.path.join(this.tmpdir, "dummy.html")
- f = open(dummy_file, "w")
- f.write("<html><body onload=\"window.close()\" /></html>\n")
- f.close()
- logging.info("running dummy firefox under profile, for initial component-registration")
- subprocess.Popen ([this.bin, "-P", this.profile, "file://" + dummy_file],
- cwd=this.tmpdir, shell=False, env=this.environ).wait()
- def run_normal(this, url):
- ff_proc = subprocess.Popen ([this.bin, "-P", this.profile, url],
- cwd=this.tmpdir, shell=False, env=this.environ)
- logging.info("started 'firefox-bin ... %s' process (pid %d)"
- % (url, ff_proc.pid))
- return ff_proc
- def run_valgrind(this, vg_tool, url):
- vg_proc = subprocess.Popen (["valgrind",
- "--tool=" + vg_tool,
- "--log-file=valgrind-" + vg_tool + "-log",
- this.bin, "-P", this.profile, url],
- cwd=this.tmpdir, shell=False, env=this.environ)
- logging.info("started 'valgrind --tool=%s firefox-bin ... %s' process (pid %d)"
- % (vg_tool, url, vg_proc.pid))
- return vg_proc
- ########################################################################
- # homebrew memory monitor until valgrind works properly
- ########################################################################
- class sampler:
- def __init__(self, name):
- self.name = name
- self.max = 0
- self.final = 0
- self.sum = 0
- self.samples = 0
- def sample(self):
- s = self.get_sample()
- self.samples += 1
- self.sum += s
- self.max = max(self.max, s)
- self.final = s
- def report(self):
- self.samples = max(self.samples, 1)
- logging.info("__COUNT_%s_MAX!%d" % (self.name.upper(), self.max))
- logging.info("__COUNT_%s_AVG!%d" % (self.name.upper(), (self.sum / self.samples)))
- logging.info("__COUNT_%s_FINAL!%d" % (self.name.upper(), self.final))
- class proc_maps_heap_sampler(sampler):
- def __init__(self, procpath):
- sampler.__init__(self, "heap")
- self.procpath = procpath
- def get_sample(self):
- map_entry_rx = re.compile("^([0-9a-f]+)-([0-9a-f]+)\s+[-r][-w][-x][-p]\s+(?:\S+\s+){3}\[heap\]$")
- maps_path = os.path.join(self.procpath, "maps")
- maps_file = open(maps_path)
- for line in maps_file.xreadlines():
- m = map_entry_rx.match(line)
- if m:
- (lo,hi) = m.group(1,2)
- lo = int(lo, 16)
- hi = int(hi, 16)
- sz = hi - lo
- maps_file.close()
- return sz
- maps_file.close()
- return 0
- class proc_status_sampler(sampler):
-
- def __init__(self, procpath, entry):
- sampler.__init__(self, entry)
- self.status_entry_rx = re.compile("^Vm%s:\s+(\d+) kB" % entry)
- self.procpath = procpath
- def get_sample(self):
- status_path = os.path.join(self.procpath, "status")
- status_file = open(status_path)
- for line in status_file.xreadlines():
- m = self.status_entry_rx.match(line)
- if m:
- status_file.close()
- return int(m.group(1)) * 1024
- status_file.close()
- return 0
- def wait_collecting_memory_stats(process):
- procdir = os.path.join("/proc", str(process.pid))
- samplers = [ proc_status_sampler(procdir, "RSS"),
- proc_status_sampler(procdir, "Size"),
- proc_maps_heap_sampler(procdir) ]
- process.poll()
- while process.returncode == None:
- for s in samplers:
- s.sample()
- time.sleep(1)
- process.poll()
- for s in samplers:
- s.report()
- ########################################################################
- # config variables
- ########################################################################
- disp = 25
- batchprefix = "batch-"
- homedir = "/home/mozvalgrind"
- vnc2swfpath = "%s/pyvnc2swf-0.8.2" % homedir
- url = "file://%s/workload.xml" % homedir
- probe_point = "nsWindow::SetTitle(*"
- num_frames = 10
- vgopts = [ "--memcheck:leak-check=yes",
- "--memcheck:error-limit=no",
- ("--memcheck:num-callers=%d" % num_frames),
- # "--memcheck:leak-resolution=high",
- # "--memcheck:show-reachable=yes",
- "--massif:format=html",
- ("--massif:depth=%d" % num_frames),
- "--massif:instrs=yes",
- "--callgrind:simulate-cache=yes",
- "--callgrind:simulate-hwpref=yes",
- # ("--callgrind:dump-before=%s" % probe_point),
- "--callgrind:I1=65536,2,64",
- "--callgrind:D1=65536,2,64",
- "--callgrind:L2=524288,8,64",
- "--verbose"
- ]
- ######################################################
- # logging results
- ######################################################
- def archive_dir(dir, sums):
- res = "current"
- tar = res + ".tar.gz"
- if os.path.exists(res):
- shutil.rmtree(res)
- if os.path.exists(tar):
- os.unlink(tar)
- os.mkdir(res)
- ix = open(os.path.join(res, "index.html"), "w")
- ix.write("<html>\n<body>\n")
- ix.write("<h1>run: %s</h1>\n" % dir)
- # summary info
- ix.write("<h2>Summary info</h2>\n")
- ix.write("<table>\n")
- for x in sums:
- ix.write("<tr><td>%s</td><td>%s</td></tr>\n" % (x, sums[x]))
- ix.write("</table>\n")
- # primary logs
- ix.write("<h2>Primary logs</h2>\n")
- for log in glob.glob(os.path.join(dir, "valgrind-*-log*")):
- (dirname, basename) = os.path.split(log)
- shutil.copy(log, os.path.join(res, basename))
- ix.write("<a href=\"%s\">%s</a><br />\n" % (basename, basename))
- # massif graphs
- ix.write("<h2>Massif results</h2>\n")
- ix.write("<h3>Click graph to see details</h3>\n")
- for mp in glob.glob(os.path.join(dir, "massif.*.ps")):
- (dirname,basename) = os.path.split(mp)
- (base,ext) = os.path.splitext(basename)
- png = base + ".png"
- html = base + ".html"
- subprocess.call(["convert", "-rotate", "270", mp, os.path.join(res, png)])
- shutil.copy(os.path.join(dir, html), os.path.join(res, html))
- ix.write("<a href=\"%s\"><img src=\"%s\" /></a><br />\n" % (html, png))
- # run movies
- ix.write("<h2>Movies</h2>\n")
- for movie in ["memcheck", "massif", "callgrind"]:
- for ext in [".html", ".swf"]:
- base = movie + ext
- if os.path.exists(os.path.join(dir, base)):
- shutil.copy(os.path.join(dir, base), os.path.join(res, base))
- if os.path.exists(os.path.join(res, movie + ".html")):
- ix.write("<a href=\"%s\">%s movie</a><br />\n" % (movie + ".html", movie))
- # callgrind profile
- ix.write("<h2>Callgrind profiles</h2>\n")
- for cg in glob.glob(os.path.join(dir, "callgrind.out.*")):
- (dir, base) = os.path.split(cg)
- shutil.copy(cg, os.path.join(res, base))
- ix.write("<a href=\"%s\">%s</a><br />\n" % (base, base))
- ix.write("</body>\n</html>\n")
- ix.close()
- for i in glob.glob(os.path.join(res, "*")):
- os.chmod(i, 0644)
- os.chmod(res, 0755)
- subprocess.call(["tar", "czvf", tar, res])
- os.chmod(tar, 0644)
- def log_result_summaries(tmpdir):
- pats = {
- "IR" : re.compile("==\d+==\s+I\s+refs:\s+([0-9,]+)"),
- "ALLOC" : re.compile("==\d+==\s+malloc/free:\s+[0-9,]+\s+allocs,\s+[0-9,]+\s+frees,\s+([0-9,]+)\s+bytes allocated."),
- "LEAKED" : re.compile("==\d+==\s+(?:definitely|indirectly)\s+lost:\s+([0-9,]+)\s+bytes"),
- "DUBIOUS" : re.compile("==\d+==\s+possibly\s+lost:\s+([0-9,]+)\s+bytes"),
- "LIVE" : re.compile("==\d+==\s+still\s+reachable:\s+([0-9,]+)\s+bytes"),
- }
- sums = {}
- for fname in glob.glob("%s/valgrind-*-log*" % tmpdir):
- f = open(fname)
- for line in f.xreadlines():
- for pat in pats:
- rx = pats[pat]
- match = rx.search(line)
- if match:
- val = int(match.group(1).replace(",", ""))
- if pat in sums:
- val = val + sums[pat]
- sums[pat] = val
- f.close()
- for pat in sums:
- logging.info("__COUNT_%s!%d" % (pat, sums[pat]))
- archive_dir(tmpdir, sums)
- ########################################################################
- # main
- ########################################################################
- if len(sys.argv) != 2:
- print("usage: %s <firefox-bin build dir>" % sys.argv[0])
- sys.exit(1)
- logging.basicConfig(level=logging.INFO,
- format='%(asctime)s %(levelname)s %(message)s')
- logging.info("running as %s" % getpass.getuser())
- logging.info("killing any residual processes in this group")
- subprocess.call(["killall", "-q", "-9", "memcheck", "callgrind", "massif", "valgrind", "firefox-bin"])
- time.sleep(3)
- dirs=glob.glob(os.path.join(homedir, batchprefix + "*"))
- dirs.sort()
- if len(dirs) > 5:
- for olddir in dirs[:-5]:
- logging.info("cleaning up old directory %s" % olddir)
- shutil.rmtree(olddir)
-
- timestr = datetime.datetime.now().isoformat().replace(":", "-")
- tmpdir = tempfile.mkdtemp(prefix=(batchprefix + timestr + "-"), dir=homedir)
- logging.info("started batch run in dir " + tmpdir)
- os.chdir(tmpdir)
- ffdir = sys.argv[1]
- write_vgopts(tmpdir, vgopts)
- xvnc_proc = None
- runner = None
- recorder = None
- ######################################################
- # note: runit is supervising a single Xvnc on disp 25
- # there is no need to run one here as well
- ######################################################
- # xvnc_proc = start_xvnc(disp, tmpdir)
- ######################################################
- try:
- runner = Firefox_runner(batchprefix, homedir, ffdir, timestr, tmpdir, disp)
- wait_collecting_memory_stats(runner.run_normal(url))
- runner.clear_cache()
- # for variant in ["memcheck", "massif", "callgrind"]:
- # recorder = start_xvnc_recorder(vnc2swfpath, disp, variant, tmpdir)
- # runner.run_valgrind(variant, url).wait()
- # runner.clear_cache()
- # complete_xvnc_recording(vnc2swfpath, recorder, variant, tmpdir)
- log_result_summaries(tmpdir)
- logging.info("valgrind-firefox processes complete")
- finally:
- if recorder and recorder.poll() == None:
- logging.info("killing recorder process %d" % recorder.pid)
- os.kill(recorder.pid, signal.SIGKILL)
- if xvnc_proc and xvnc_proc.poll() == None:
- logging.info("killing Xvnc process %d" % xvnc_proc.pid)
- os.kill(xvnc_proc.pid, signal.SIGKILL)
- logging.info("batch run in dir %s complete" % tmpdir)