Bug 1082265 - Rewrite split-profile.pl in python.
authorL. David Baron <dbaron@dbaron.org>
Mon, 13 Oct 2014 18:20:21 -0700
changeset 210273 a5282aa9aad794dba714e90af0dfc63c49e508c4
parent 210272 f5c56af59b28d1b5df7e0eeeddc159e4f29d63a8
child 210274 35029c909c0307af693ebbc94e9191f56c581e8e
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
bugs1082265
milestone35.0a1
Bug 1082265 - Rewrite split-profile.pl in python. This adds arguments for the paths to jprof, the program being profiled, and the jprof profile itself, so I don't need to modify my local copy of split-profile.pl to fix those. DONTBUILD
tools/jprof/split-profile.pl
tools/jprof/split-profile.py
deleted file mode 100755
--- a/tools/jprof/split-profile.pl
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/perl
-# 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/.
-
-
-# split-profile.pl Documentation:
-# 
-# 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 in which it is run.  It expects the application binaries
-# with which the profile was made, including jprof, and the jprof
-# profile data, to be in a directory called "bin" that is a subdirectory
-# of the current directory, and it will output the profiles into the
-# current directory.
-#
-# 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
-# 00.html that contains the whole profile, a profile called 01-poll.html
-# that includes only stacks with g_main_poll, a profile called
-# 02-GetRuleCascade.html that includes only stacks that have
-# GetRuleCascade and do not have g_main_poll, a profile called
-# 03-RuleProcessorData.html that includes only stacks that have the
-# RuleProcessorData constructor and do not have GetRuleCascade or
-# g_main_poll, and a profile called 04.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 00.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.
-
-
-use strict;
-
-my @names;
-my @sigs;
-
-sub read_info($) {
-    my ($fname) = @_;
-
-    open(INFO, "<$fname");
-    my $i = 0;
-    while (<INFO>) {
-        chop;
-        my $line = $_;
-        my $idx = index($line, " ");
-        my $name = substr($line, 0, $idx);
-        my $sig = substr($line, $idx+1);
-
-        $names[$i] = $name;
-        $sigs[$i] = $sig;
-        ++$i;
-    }
-}
-
-sub run_profile($$) {
-    my ($options, $outfile) = @_;
-
-    print  "./jprof$options mozilla-bin jprof-log > ../$outfile.html\n";
-    system "./jprof$options mozilla-bin jprof-log > ../$outfile.html";
-}
-
-sub run_profiles() {
-    run_profile("", "00");
-
-    for (my $i = 0; $i <= $#names + 1; ++$i) {
-        my $options = "";
-        for (my $j = 0; $j < $i; ++$j) {
-            $options .= " -e\"$sigs[$j]\"";
-        }
-        if ($i <= $#names) {
-            $options .= " -i\"$sigs[$i]\"";
-        }
-        my $num;
-        my $n = $i + 1;
-        if ($n < 10) {
-            $num = "0$n";
-        } else {
-            $num = "$n";
-        }
-        if ($i <= $#names) {
-            run_profile($options, "$num-$names[$i]");
-        } else {
-            run_profile($options, "$num");
-        }
-    }
-}
-
-($#ARGV == 0) || die "Usage: split-profile.pl <info-file>\n";
-
-read_info($ARGV[0]);
-chdir "bin" || die "Can't change directory to bin.";
-run_profiles();
new file mode 100755
--- /dev/null
+++ b/tools/jprof/split-profile.py
@@ -0,0 +1,140 @@
+#!/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 in which it is run.  It expects the application binaries
+# with which the profile was made, including jprof, and the jprof
+# profile data, to be in a directory called "bin" that is a subdirectory
+# of the current directory, and it will output the profiles into the
+# current directory.
+#
+# 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")
+    process = subprocess.Popen(args, stdout=destio)
+    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))