diff options
Diffstat (limited to 'tools/jprof/split-profile.py')
-rwxr-xr-x | tools/jprof/split-profile.py | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/tools/jprof/split-profile.py b/tools/jprof/split-profile.py new file mode 100755 index 000000000..89454d3eb --- /dev/null +++ b/tools/jprof/split-profile.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This program splits up a jprof profile into multiple files based on a +# list of functions in a text file. First, a complete profile is +# generated. Then, for each line in the text file, a profile is +# generated containing only stacks that go through that line, and also +# excluding all stacks in earlier lines in the text file. This means +# that the text file, from start to end, is splitting out pieces of the +# profile in their own file. Finally, a final profile containing the +# remainder is produced. + +# The program takes four arguments: +# (1) The path to jprof. +# (2) The path to the text file describing the splits. The output +# will be placed in the same directory as this file. +# (3) The program that was profiled. +# (4) The jprof-log file generated by the profile, to be split up. +# (Really, all arguments from (3) and later are passed through to +# jprof, so additional arguments could be provided if you want to pass +# additional arguments to jprof.) + +# In slightly more detail: +# +# This script uses jprof's includes (-i) and excludes (-e) options to +# split profiles into segments. It takes as input a single text file, +# and from that text file creates a series of jprof profiles in the +# directory the text file is in. +# +# The input file format looks like the following: +# +# poll g_main_poll +# GetRuleCascade CSSRuleProcessor::GetRuleCascade(nsPresContext *, nsIAtom *) +# RuleProcessorData RuleProcessorData::RuleProcessorData(nsPresContext *, nsIContent *, nsRuleWalker *, nsCompatibility *) +# +# From this input file, the script will construct a profile called +# jprof-0.html that contains the whole profile, a profile called +# jprof-1-poll.html that includes only stacks with g_main_poll, a +# profile called jprof-2-GetRuleCascade.html that includes only stacks +# that have GetRuleCascade and do not have g_main_poll, a profile called +# jprof-3-RuleProcessorData.html that includes only stacks that have the +# RuleProcessorData constructor and do not have GetRuleCascade or +# g_main_poll, and a profile called jprof-4.html that includes only +# stacks that do not have any of the three functions in them. +# +# This means that all of the segments of the profile, except +# jprof-0.html, are mutually exclusive. Thus clever ordering of the +# functions in the input file can lead to a logical splitting of the +# profile into segments. + +import sys +import subprocess +import os.path + +if len(sys.argv) < 5: + sys.stderr.write("Expected arguments: <jprof> <split-file> <program> <jprof-log>\n") + sys.exit(1) + +jprof = sys.argv[1] +splitfile = sys.argv[2] +passthrough = sys.argv[3:] + +for f in [jprof, splitfile]: + if not os.path.isfile(f): + sys.stderr.write("could not find file: {0}\n".format(f)) + sys.exit(1) + +def read_splits(splitfile): + """ + Read splitfile (each line of which contains a name, a space, and + then a function name to split on), and return a list of pairs + representing exactly that. (Note that the name cannot contain + spaces, but the function name can, and often does.) + """ + def line_to_split(line): + line = line.strip("\r\n") + idx = line.index(" ") + return (line[0:idx], line[idx+1:]) + + io = open(splitfile, "r") + result = [line_to_split(line) for line in io] + io.close() + return result + +splits = read_splits(splitfile) + +def generate_profile(options, destfile): + """ + Run jprof to generate one split of the profile. + """ + args = [jprof] + options + passthrough + print "Generating {0}".format(destfile) + destio = open(destfile, "w") + # jprof expects the "jprof-map" file to be in its current working directory + cwd = None + for option in passthrough: + if option.find("jprof-log"): + cwd = os.path.dirname(option) + if cwd is None: + raise StandardError("no jprof-log option given") + process = subprocess.Popen(args, stdout=destio, cwd=cwd) + process.wait() + destio.close() + if process.returncode != 0: + os.remove(destfile) + sys.stderr.write("Error {0} from command:\n {1}\n".format(process.returncode, " ".join(args))) + sys.exit(process.returncode) + +def output_filename(number, splitname): + """ + Return the filename (absolute path) we should use to output the + profile segment with the given number and splitname. Splitname + should be None for the complete profile and the remainder. + """ + def pad_count(i): + result = str(i) + # 0-pad to the same length + result = "0" * (len(str(len(splits) + 1)) - len(result)) + result + return result + + name = pad_count(number) + if splitname is not None: + name += "-" + splitname + + return os.path.join(os.path.dirname(splitfile), + "jprof-{0}.html".format(name)) + +# generate the complete profile +generate_profile([], output_filename(0, None)) + +# generate the listed splits +count = 1 +excludes = [] +for (splitname, splitfunction) in splits: + generate_profile(excludes + ["-i" + splitfunction], + output_filename(count, splitname)) + excludes += ["-e" + splitfunction] + count = count + 1 + +# generate the remainder after the splits +generate_profile(excludes, output_filename(count, None)) |