PageRenderTime 63ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/testing/tests/memtest.py

https://bitbucket.org/soko/mozilla-central
Python | 534 lines | 391 code | 81 blank | 62 comment | 24 complexity | 9abfda783a947bea6d1cc73681707ecf MD5 | raw file
Possible License(s): GPL-2.0, JSON, 0BSD, LGPL-3.0, AGPL-1.0, MIT, MPL-2.0-no-copyleft-exception, BSD-3-Clause, LGPL-2.1, Apache-2.0
  1. #!/usr/bin/python
  2. #
  3. # ***** BEGIN LICENSE BLOCK *****
  4. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  5. #
  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/
  10. #
  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
  14. # License.
  15. #
  16. # The Original Code is standalone Firefox memory test.
  17. #
  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.
  22. #
  23. # Contributor(s):
  24. # Graydon Hoare <graydon@mozilla.com> (original author)
  25. #
  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.
  37. #
  38. # ***** END LICENSE BLOCK *****
  39. import subprocess
  40. import tempfile
  41. import os
  42. import re
  43. import getpass
  44. import datetime
  45. import logging
  46. import time
  47. import sys
  48. import signal
  49. import shutil
  50. import glob
  51. def sighandler(signal, frame):
  52. print "signal %d, throwing" % signal
  53. raise Exception
  54. signal.signal(signal.SIGHUP, sighandler)
  55. ########################################################################
  56. def start_xvnc(disp, tmpdir):
  57. xvnc_stdout = open(os.path.join(tmpdir, "xvnc.stdout"), mode="w")
  58. xvnc_stderr = open(os.path.join(tmpdir, "xvnc.stderr"), mode="w")
  59. xvnc_proc = subprocess.Popen (["Xvnc",
  60. "-fp", "/usr/share/X11/fonts/misc",
  61. "-localhost",
  62. "-SecurityTypes", "None",
  63. "-IdleTimeout", "604800",
  64. "-depth", "24",
  65. "-ac",
  66. (":%d" % disp)],
  67. cwd=tmpdir,
  68. shell=False,
  69. stdout=xvnc_stdout,
  70. stderr=xvnc_stderr)
  71. time.sleep(3)
  72. assert xvnc_proc.poll() == None
  73. logging.info("started Xvnc server (pid %d) on display :%d"
  74. % (xvnc_proc.pid, disp))
  75. return xvnc_proc
  76. ########################################################################
  77. def large_variant_filename(variant):
  78. return variant + "-large.swf"
  79. def large_variant_html_filename(variant):
  80. return variant + "-large.html"
  81. def small_variant_filename(variant):
  82. return variant + ".swf"
  83. def start_xvnc_recorder(vnc2swfpath, disp, variant, tmpdir):
  84. rec_stdout = open(os.path.join(tmpdir, "vnc2swf.stdout"), mode="w")
  85. rec_stderr = open(os.path.join(tmpdir, "vnc2swf.stderr"), mode="w")
  86. rec_proc = subprocess.Popen ([os.path.join(vnc2swfpath, "vnc2swf.py"),
  87. "-n",
  88. "-o", large_variant_filename(variant),
  89. "-C", "800x600+0+100",
  90. "-r", "2",
  91. "-t", "video",
  92. "localhost:%d" % disp],
  93. cwd=tmpdir,
  94. shell=False,
  95. stdout=rec_stdout,
  96. stderr=rec_stderr)
  97. time.sleep(3)
  98. assert rec_proc.poll() == None
  99. logging.info("started vnc2swf recorder (pid %d) on display :%d"
  100. % (rec_proc.pid, disp))
  101. return rec_proc
  102. def complete_xvnc_recording(vnc2swfpath, proc, variant, tmpdir):
  103. edit_stdout = open(os.path.join(tmpdir, "edit.stdout"), mode="w")
  104. edit_stderr = open(os.path.join(tmpdir, "edit.stderr"), mode="w")
  105. logging.info("killing vnc2swf recorder (pid %d)" % proc.pid)
  106. os.kill(proc.pid, signal.SIGINT)
  107. proc.wait()
  108. logging.info("down-sampling video")
  109. subprocess.Popen([os.path.join(vnc2swfpath, "edit.py"),
  110. "-c",
  111. "-o", small_variant_filename(variant),
  112. "-t", "video",
  113. "-s", "0.5",
  114. "-r", "32",
  115. large_variant_filename(variant)],
  116. cwd=tmpdir,
  117. shell=False,
  118. stdout=edit_stdout,
  119. stderr=edit_stderr).wait()
  120. #os.unlink(large_variant_html_filename(variant))
  121. #os.unlink(large_variant_filename(variant))
  122. logging.info("output video is in " + small_variant_filename(variant))
  123. ########################################################################
  124. def write_vgopts(tmpdir, vgopts):
  125. f = open(os.path.join(tmpdir, ".valgrindrc"), "w")
  126. for i in vgopts:
  127. f.write(i + "\n")
  128. f.close()
  129. ########################################################################
  130. class Firefox_runner:
  131. def __init__(this, batchprefix, homedir, ffdir, timestr, tmpdir, disp):
  132. this.tmpdir = tmpdir
  133. this.homedir = homedir
  134. this.ffdir = ffdir
  135. this.profile = batchprefix + timestr
  136. this.profile_dir = os.path.join(this.tmpdir, this.profile)
  137. this.bin = os.path.join(this.ffdir, "firefox-bin")
  138. this.environ = {
  139. "PATH" : os.getenv("PATH"),
  140. "HOME" : this.homedir,
  141. "LD_LIBRARY_PATH" : this.ffdir,
  142. "MOZ_NO_REMOTE" : "1",
  143. # "DISPLAY" : ":0",
  144. "DISPLAY" : (":%d" % disp),
  145. }
  146. this.kill_initial_profiles_ini()
  147. this.create_tmp_profile()
  148. this.write_proxy_pac_file()
  149. this.write_user_prefs()
  150. this.perform_initial_registration()
  151. def kill_initial_profiles_ini(this):
  152. prof = os.path.join(this.homedir, ".mozilla")
  153. shutil.rmtree(prof, True)
  154. os.mkdir(prof)
  155. def clear_cache(this):
  156. shutil.rmtree(os.path.join(this.profile_dir, "Cache"), True)
  157. def create_tmp_profile(this):
  158. subprocess.Popen ([this.bin, "-CreateProfile", (this.profile + " " + this.profile_dir)],
  159. cwd=this.tmpdir, shell=False, env=this.environ).wait()
  160. def write_proxy_pac_file(this):
  161. this.proxypac = os.path.join(this.tmpdir, "proxy.pac")
  162. p = open(this.proxypac, "w")
  163. p.write("function FindProxyForURL(url, host) {\n"
  164. " if (host == \"localhost\")\n"
  165. " return \"DIRECT\";\n"
  166. " else\n"
  167. " return \"PROXY localhost\";\n"
  168. "}\n")
  169. p.close()
  170. def write_user_prefs(this):
  171. userprefs = open(os.path.join(this.profile_dir, "user.js"), "w")
  172. userprefs.write("user_pref(\"network.proxy.autoconfig_url\", \"file://%s\");\n" % this.proxypac)
  173. userprefs.write("user_pref(\"network.proxy.type\", 2);\n")
  174. userprefs.write("user_pref(\"dom.max_script_run_time\", 0);\n")
  175. userprefs.write("user_pref(\"hangmonitor.timeout\", 0);\n");
  176. userprefs.write("user_pref(\"dom.allow_scripts_to_close_windows\", true);\n")
  177. userprefs.close()
  178. def perform_initial_registration(this):
  179. dummy_file = os.path.join(this.tmpdir, "dummy.html")
  180. f = open(dummy_file, "w")
  181. f.write("<html><body onload=\"window.close()\" /></html>\n")
  182. f.close()
  183. logging.info("running dummy firefox under profile, for initial component-registration")
  184. subprocess.Popen ([this.bin, "-P", this.profile, "file://" + dummy_file],
  185. cwd=this.tmpdir, shell=False, env=this.environ).wait()
  186. def run_normal(this, url):
  187. ff_proc = subprocess.Popen ([this.bin, "-P", this.profile, url],
  188. cwd=this.tmpdir, shell=False, env=this.environ)
  189. logging.info("started 'firefox-bin ... %s' process (pid %d)"
  190. % (url, ff_proc.pid))
  191. return ff_proc
  192. def run_valgrind(this, vg_tool, url):
  193. vg_proc = subprocess.Popen (["valgrind",
  194. "--tool=" + vg_tool,
  195. "--log-file=valgrind-" + vg_tool + "-log",
  196. this.bin, "-P", this.profile, url],
  197. cwd=this.tmpdir, shell=False, env=this.environ)
  198. logging.info("started 'valgrind --tool=%s firefox-bin ... %s' process (pid %d)"
  199. % (vg_tool, url, vg_proc.pid))
  200. return vg_proc
  201. ########################################################################
  202. # homebrew memory monitor until valgrind works properly
  203. ########################################################################
  204. class sampler:
  205. def __init__(self, name):
  206. self.name = name
  207. self.max = 0
  208. self.final = 0
  209. self.sum = 0
  210. self.samples = 0
  211. def sample(self):
  212. s = self.get_sample()
  213. self.samples += 1
  214. self.sum += s
  215. self.max = max(self.max, s)
  216. self.final = s
  217. def report(self):
  218. self.samples = max(self.samples, 1)
  219. logging.info("__COUNT_%s_MAX!%d" % (self.name.upper(), self.max))
  220. logging.info("__COUNT_%s_AVG!%d" % (self.name.upper(), (self.sum / self.samples)))
  221. logging.info("__COUNT_%s_FINAL!%d" % (self.name.upper(), self.final))
  222. class proc_maps_heap_sampler(sampler):
  223. def __init__(self, procpath):
  224. sampler.__init__(self, "heap")
  225. self.procpath = procpath
  226. def get_sample(self):
  227. map_entry_rx = re.compile("^([0-9a-f]+)-([0-9a-f]+)\s+[-r][-w][-x][-p]\s+(?:\S+\s+){3}\[heap\]$")
  228. maps_path = os.path.join(self.procpath, "maps")
  229. maps_file = open(maps_path)
  230. for line in maps_file.xreadlines():
  231. m = map_entry_rx.match(line)
  232. if m:
  233. (lo,hi) = m.group(1,2)
  234. lo = int(lo, 16)
  235. hi = int(hi, 16)
  236. sz = hi - lo
  237. maps_file.close()
  238. return sz
  239. maps_file.close()
  240. return 0
  241. class proc_status_sampler(sampler):
  242. def __init__(self, procpath, entry):
  243. sampler.__init__(self, entry)
  244. self.status_entry_rx = re.compile("^Vm%s:\s+(\d+) kB" % entry)
  245. self.procpath = procpath
  246. def get_sample(self):
  247. status_path = os.path.join(self.procpath, "status")
  248. status_file = open(status_path)
  249. for line in status_file.xreadlines():
  250. m = self.status_entry_rx.match(line)
  251. if m:
  252. status_file.close()
  253. return int(m.group(1)) * 1024
  254. status_file.close()
  255. return 0
  256. def wait_collecting_memory_stats(process):
  257. procdir = os.path.join("/proc", str(process.pid))
  258. samplers = [ proc_status_sampler(procdir, "RSS"),
  259. proc_status_sampler(procdir, "Size"),
  260. proc_maps_heap_sampler(procdir) ]
  261. process.poll()
  262. while process.returncode == None:
  263. for s in samplers:
  264. s.sample()
  265. time.sleep(1)
  266. process.poll()
  267. for s in samplers:
  268. s.report()
  269. ########################################################################
  270. # config variables
  271. ########################################################################
  272. disp = 25
  273. batchprefix = "batch-"
  274. homedir = "/home/mozvalgrind"
  275. vnc2swfpath = "%s/pyvnc2swf-0.8.2" % homedir
  276. url = "file://%s/workload.xml" % homedir
  277. probe_point = "nsWindow::SetTitle(*"
  278. num_frames = 10
  279. vgopts = [ "--memcheck:leak-check=yes",
  280. "--memcheck:error-limit=no",
  281. ("--memcheck:num-callers=%d" % num_frames),
  282. # "--memcheck:leak-resolution=high",
  283. # "--memcheck:show-reachable=yes",
  284. "--massif:format=html",
  285. ("--massif:depth=%d" % num_frames),
  286. "--massif:instrs=yes",
  287. "--callgrind:simulate-cache=yes",
  288. "--callgrind:simulate-hwpref=yes",
  289. # ("--callgrind:dump-before=%s" % probe_point),
  290. "--callgrind:I1=65536,2,64",
  291. "--callgrind:D1=65536,2,64",
  292. "--callgrind:L2=524288,8,64",
  293. "--verbose"
  294. ]
  295. ######################################################
  296. # logging results
  297. ######################################################
  298. def archive_dir(dir, sums):
  299. res = "current"
  300. tar = res + ".tar.gz"
  301. if os.path.exists(res):
  302. shutil.rmtree(res)
  303. if os.path.exists(tar):
  304. os.unlink(tar)
  305. os.mkdir(res)
  306. ix = open(os.path.join(res, "index.html"), "w")
  307. ix.write("<html>\n<body>\n")
  308. ix.write("<h1>run: %s</h1>\n" % dir)
  309. # summary info
  310. ix.write("<h2>Summary info</h2>\n")
  311. ix.write("<table>\n")
  312. for x in sums:
  313. ix.write("<tr><td>%s</td><td>%s</td></tr>\n" % (x, sums[x]))
  314. ix.write("</table>\n")
  315. # primary logs
  316. ix.write("<h2>Primary logs</h2>\n")
  317. for log in glob.glob(os.path.join(dir, "valgrind-*-log*")):
  318. (dirname, basename) = os.path.split(log)
  319. shutil.copy(log, os.path.join(res, basename))
  320. ix.write("<a href=\"%s\">%s</a><br />\n" % (basename, basename))
  321. # massif graphs
  322. ix.write("<h2>Massif results</h2>\n")
  323. ix.write("<h3>Click graph to see details</h3>\n")
  324. for mp in glob.glob(os.path.join(dir, "massif.*.ps")):
  325. (dirname,basename) = os.path.split(mp)
  326. (base,ext) = os.path.splitext(basename)
  327. png = base + ".png"
  328. html = base + ".html"
  329. subprocess.call(["convert", "-rotate", "270", mp, os.path.join(res, png)])
  330. shutil.copy(os.path.join(dir, html), os.path.join(res, html))
  331. ix.write("<a href=\"%s\"><img src=\"%s\" /></a><br />\n" % (html, png))
  332. # run movies
  333. ix.write("<h2>Movies</h2>\n")
  334. for movie in ["memcheck", "massif", "callgrind"]:
  335. for ext in [".html", ".swf"]:
  336. base = movie + ext
  337. if os.path.exists(os.path.join(dir, base)):
  338. shutil.copy(os.path.join(dir, base), os.path.join(res, base))
  339. if os.path.exists(os.path.join(res, movie + ".html")):
  340. ix.write("<a href=\"%s\">%s movie</a><br />\n" % (movie + ".html", movie))
  341. # callgrind profile
  342. ix.write("<h2>Callgrind profiles</h2>\n")
  343. for cg in glob.glob(os.path.join(dir, "callgrind.out.*")):
  344. (dir, base) = os.path.split(cg)
  345. shutil.copy(cg, os.path.join(res, base))
  346. ix.write("<a href=\"%s\">%s</a><br />\n" % (base, base))
  347. ix.write("</body>\n</html>\n")
  348. ix.close()
  349. for i in glob.glob(os.path.join(res, "*")):
  350. os.chmod(i, 0644)
  351. os.chmod(res, 0755)
  352. subprocess.call(["tar", "czvf", tar, res])
  353. os.chmod(tar, 0644)
  354. def log_result_summaries(tmpdir):
  355. pats = {
  356. "IR" : re.compile("==\d+==\s+I\s+refs:\s+([0-9,]+)"),
  357. "ALLOC" : re.compile("==\d+==\s+malloc/free:\s+[0-9,]+\s+allocs,\s+[0-9,]+\s+frees,\s+([0-9,]+)\s+bytes allocated."),
  358. "LEAKED" : re.compile("==\d+==\s+(?:definitely|indirectly)\s+lost:\s+([0-9,]+)\s+bytes"),
  359. "DUBIOUS" : re.compile("==\d+==\s+possibly\s+lost:\s+([0-9,]+)\s+bytes"),
  360. "LIVE" : re.compile("==\d+==\s+still\s+reachable:\s+([0-9,]+)\s+bytes"),
  361. }
  362. sums = {}
  363. for fname in glob.glob("%s/valgrind-*-log*" % tmpdir):
  364. f = open(fname)
  365. for line in f.xreadlines():
  366. for pat in pats:
  367. rx = pats[pat]
  368. match = rx.search(line)
  369. if match:
  370. val = int(match.group(1).replace(",", ""))
  371. if pat in sums:
  372. val = val + sums[pat]
  373. sums[pat] = val
  374. f.close()
  375. for pat in sums:
  376. logging.info("__COUNT_%s!%d" % (pat, sums[pat]))
  377. archive_dir(tmpdir, sums)
  378. ########################################################################
  379. # main
  380. ########################################################################
  381. if len(sys.argv) != 2:
  382. print("usage: %s <firefox-bin build dir>" % sys.argv[0])
  383. sys.exit(1)
  384. logging.basicConfig(level=logging.INFO,
  385. format='%(asctime)s %(levelname)s %(message)s')
  386. logging.info("running as %s" % getpass.getuser())
  387. logging.info("killing any residual processes in this group")
  388. subprocess.call(["killall", "-q", "-9", "memcheck", "callgrind", "massif", "valgrind", "firefox-bin"])
  389. time.sleep(3)
  390. dirs=glob.glob(os.path.join(homedir, batchprefix + "*"))
  391. dirs.sort()
  392. if len(dirs) > 5:
  393. for olddir in dirs[:-5]:
  394. logging.info("cleaning up old directory %s" % olddir)
  395. shutil.rmtree(olddir)
  396. timestr = datetime.datetime.now().isoformat().replace(":", "-")
  397. tmpdir = tempfile.mkdtemp(prefix=(batchprefix + timestr + "-"), dir=homedir)
  398. logging.info("started batch run in dir " + tmpdir)
  399. os.chdir(tmpdir)
  400. ffdir = sys.argv[1]
  401. write_vgopts(tmpdir, vgopts)
  402. xvnc_proc = None
  403. runner = None
  404. recorder = None
  405. ######################################################
  406. # note: runit is supervising a single Xvnc on disp 25
  407. # there is no need to run one here as well
  408. ######################################################
  409. # xvnc_proc = start_xvnc(disp, tmpdir)
  410. ######################################################
  411. try:
  412. runner = Firefox_runner(batchprefix, homedir, ffdir, timestr, tmpdir, disp)
  413. wait_collecting_memory_stats(runner.run_normal(url))
  414. runner.clear_cache()
  415. # for variant in ["memcheck", "massif", "callgrind"]:
  416. # recorder = start_xvnc_recorder(vnc2swfpath, disp, variant, tmpdir)
  417. # runner.run_valgrind(variant, url).wait()
  418. # runner.clear_cache()
  419. # complete_xvnc_recording(vnc2swfpath, recorder, variant, tmpdir)
  420. log_result_summaries(tmpdir)
  421. logging.info("valgrind-firefox processes complete")
  422. finally:
  423. if recorder and recorder.poll() == None:
  424. logging.info("killing recorder process %d" % recorder.pid)
  425. os.kill(recorder.pid, signal.SIGKILL)
  426. if xvnc_proc and xvnc_proc.poll() == None:
  427. logging.info("killing Xvnc process %d" % xvnc_proc.pid)
  428. os.kill(xvnc_proc.pid, signal.SIGKILL)
  429. logging.info("batch run in dir %s complete" % tmpdir)