PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/testing/tests/memtest.py

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