| #!/usr/bin/env ruby | |
| # Copyright (C) 2012, 2013 Apple Inc. All rights reserved. | |
| # | |
| # Redistribution and use in source and binary forms, with or without | |
| # modification, are permitted provided that the following conditions | |
| # are met: | |
| # 1. Redistributions of source code must retain the above copyright | |
| # notice, this list of conditions and the following disclaimer. | |
| # 2. Redistributions in binary form must reproduce the above copyright | |
| # notice, this list of conditions and the following disclaimer in the | |
| # documentation and/or other materials provided with the distribution. | |
| # | |
| # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' | |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | |
| # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS | |
| # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | |
| # THE POSSIBILITY OF SUCH DAMAGE. | |
| require 'rubygems' | |
| require 'readline' | |
| begin | |
| require 'json' | |
| require 'highline' | |
| rescue LoadError | |
| $stderr.puts "Error: some required gems are not installed!" | |
| $stderr.puts | |
| $stderr.puts "Try running:" | |
| $stderr.puts | |
| $stderr.puts "sudo gem install json" | |
| $stderr.puts "sudo gem install highline" | |
| exit 1 | |
| end | |
| class Bytecode | |
| attr_accessor :bytecodes, :bytecodeIndex, :opcode, :description, :topCounts, :bottomCounts, :machineInlinees, :osrExits | |
| def initialize(bytecodes, bytecodeIndex, opcode, description) | |
| @bytecodes = bytecodes | |
| @bytecodeIndex = bytecodeIndex | |
| @opcode = opcode | |
| @description = description | |
| @topCounts = [] # "source" counts | |
| @bottomCounts = {} # "machine" counts, maps compilations to counts | |
| @machineInlinees = {} # maps my compilation to a set of inlinees | |
| @osrExits = [] | |
| end | |
| def shouldHaveCounts? | |
| @opcode != "op_call_put_result" | |
| end | |
| def addTopCount(count) | |
| @topCounts << count | |
| end | |
| def addBottomCountForCompilation(count, compilation) | |
| @bottomCounts[compilation] = [] unless @bottomCounts[compilation] | |
| @bottomCounts[compilation] << count | |
| end | |
| def addMachineInlinee(compilation, inlinee) | |
| @machineInlinees[compilation] = {} unless @machineInlinees[compilation] | |
| @machineInlinees[compilation][inlinee] = true | |
| end | |
| def totalTopExecutionCount | |
| sum = 0 | |
| @topCounts.each { | |
| | value | | |
| sum += value.count | |
| } | |
| sum | |
| end | |
| def topExecutionCount(engine) | |
| sum = 0 | |
| @topCounts.each { | |
| | value | | |
| if value.engine == engine | |
| sum += value.count | |
| end | |
| } | |
| sum | |
| end | |
| def totalBottomExecutionCount | |
| sum = 0 | |
| @bottomCounts.each_value { | |
| | counts | | |
| max = 0 | |
| counts.each { | |
| | value | | |
| max = [max, value.count].max | |
| } | |
| sum += max | |
| } | |
| sum | |
| end | |
| def bottomExecutionCount(engine) | |
| sum = 0 | |
| @bottomCounts.each_pair { | |
| | compilation, counts | | |
| if compilation.engine == engine | |
| max = 0 | |
| counts.each { | |
| | value | | |
| max = [max, value.count].max | |
| } | |
| sum += max | |
| end | |
| } | |
| sum | |
| end | |
| def totalExitCount | |
| sum = 0 | |
| @osrExits.each { | |
| | exit | | |
| sum += exit.count | |
| } | |
| sum | |
| end | |
| end | |
| class Bytecodes | |
| attr_accessor :codeHash, :inferredName, :source, :instructionCount, :machineInlineSites, :compilations | |
| def initialize(json) | |
| @codeHash = json["hash"].to_s | |
| @inferredName = json["inferredName"].to_s | |
| @source = json["sourceCode"].to_s | |
| @instructionCount = json["instructionCount"].to_i | |
| @bytecode = {} | |
| json["bytecode"].each { | |
| | subJson | | |
| index = subJson["bytecodeIndex"].to_i | |
| @bytecode[index] = Bytecode.new(self, index, subJson["opcode"].to_s, subJson["description"].to_s) | |
| } | |
| @machineInlineSites = {} # maps compilation to a set of origins | |
| @compilations = [] | |
| end | |
| def name(limit) | |
| if to_s.size > limit | |
| "\##{@codeHash}" | |
| else | |
| to_s | |
| end | |
| end | |
| def to_s | |
| "#{@inferredName}\##{@codeHash}" | |
| end | |
| def matches(pattern) | |
| if pattern =~ /^#/ | |
| $~.post_match == @codeHash | |
| elsif pattern =~ /#/ | |
| pattern == to_s | |
| else | |
| pattern == @inferredName or pattern == @codeHash | |
| end | |
| end | |
| def each | |
| @bytecode.values.sort{|a, b| a.bytecodeIndex <=> b.bytecodeIndex}.each { | |
| | value | | |
| yield value | |
| } | |
| end | |
| def bytecode(bytecodeIndex) | |
| @bytecode[bytecodeIndex] | |
| end | |
| def addMachineInlineSite(compilation, origin) | |
| @machineInlineSites[compilation] = {} unless @machineInlineSites[compilation] | |
| @machineInlineSites[compilation][origin] = true | |
| end | |
| def totalMachineInlineSites | |
| sum = 0 | |
| @machineInlineSites.each_value { | |
| | set | | |
| sum += set.size | |
| } | |
| sum | |
| end | |
| def sourceMachineInlineSites | |
| set = {} | |
| @machineInlineSites.each_value { | |
| | mySet | | |
| set.merge!(mySet) | |
| } | |
| set.size | |
| end | |
| def totalMaxTopExecutionCount | |
| max = 0 | |
| @bytecode.each_value { | |
| | bytecode | | |
| max = [max, bytecode.totalTopExecutionCount].max | |
| } | |
| max | |
| end | |
| def maxTopExecutionCount(engine) | |
| max = 0 | |
| @bytecode.each_value { | |
| | bytecode | | |
| max = [max, bytecode.topExecutionCount(engine)].max | |
| } | |
| max | |
| end | |
| def totalMaxBottomExecutionCount | |
| max = 0 | |
| @bytecode.each_value { | |
| | bytecode | | |
| max = [max, bytecode.totalBottomExecutionCount].max | |
| } | |
| max | |
| end | |
| def maxBottomExecutionCount(engine) | |
| max = 0 | |
| @bytecode.each_value { | |
| | bytecode | | |
| max = [max, bytecode.bottomExecutionCount(engine)].max | |
| } | |
| max | |
| end | |
| def totalExitCount | |
| sum = 0 | |
| each { | |
| | bytecode | | |
| sum += bytecode.totalExitCount | |
| } | |
| sum | |
| end | |
| end | |
| class ProfiledBytecode | |
| attr_reader :bytecodeIndex, :description | |
| def initialize(json) | |
| @bytecodeIndex = json["bytecodeIndex"].to_i | |
| @description = json["description"].to_s | |
| end | |
| end | |
| class ProfiledBytecodes | |
| attr_reader :header, :bytecodes | |
| def initialize(json) | |
| @header = json["header"] | |
| @bytecodes = $bytecodes[json["bytecodesID"].to_i] | |
| @sequence = json["bytecode"].map { | |
| | subJson | | |
| ProfiledBytecode.new(subJson) | |
| } | |
| end | |
| def each | |
| @sequence.each { | |
| | description | | |
| yield description | |
| } | |
| end | |
| end | |
| def originStackFromJSON(json) | |
| json.map { | |
| | subJson | | |
| $bytecodes[subJson["bytecodesID"].to_i].bytecode(subJson["bytecodeIndex"].to_i) | |
| } | |
| end | |
| class CompiledBytecode | |
| attr_accessor :origin, :description | |
| def initialize(json) | |
| @origin = originStackFromJSON(json["origin"]) | |
| @description = json["description"].to_s | |
| end | |
| end | |
| class ExecutionCounter | |
| attr_accessor :origin, :engine, :count | |
| def initialize(origin, engine, count) | |
| @origin = origin | |
| @engine = engine | |
| @count = count | |
| end | |
| end | |
| class OSRExit | |
| attr_reader :compilation, :origin, :codeAddresses, :exitKind, :isWatchpoint, :count | |
| def initialize(compilation, origin, codeAddresses, exitKind, isWatchpoint, count) | |
| @compilation = compilation | |
| @origin = origin | |
| @codeAddresses = codeAddresses | |
| @exitKind = exitKind | |
| @isWatchpoint = isWatchpoint | |
| @count = count | |
| end | |
| def dumpForDisplay(prefix) | |
| puts(prefix + "EXIT: due to #{@exitKind}, #{@count} times") | |
| end | |
| end | |
| class Compilation | |
| attr_accessor :bytecode, :engine, :descriptions, :counters, :compilationIndex | |
| attr_accessor :osrExits, :profiledBytecodes, :numInlinedGetByIds, :numInlinedPutByIds | |
| attr_accessor :numInlinedCalls | |
| def initialize(json) | |
| @bytecode = $bytecodes[json["bytecodesID"].to_i] | |
| @bytecode.compilations << self | |
| @compilationIndex = @bytecode.compilations.size | |
| @engine = json["compilationKind"] | |
| @descriptions = json["descriptions"].map { | |
| | subJson | | |
| CompiledBytecode.new(subJson) | |
| } | |
| @descriptions.each { | |
| | description | | |
| next if description.origin.empty? | |
| description.origin[1..-1].each_with_index { | |
| | inlinee, index | | |
| description.origin[0].addMachineInlinee(self, inlinee.bytecodes) | |
| inlinee.bytecodes.addMachineInlineSite(self, description.origin[0...index]) | |
| } | |
| } | |
| @counters = {} | |
| json["counters"].each { | |
| | subJson | | |
| origin = originStackFromJSON(subJson["origin"]) | |
| counter = ExecutionCounter.new(origin, @engine, subJson["executionCount"].to_i) | |
| @counters[origin] = counter | |
| origin[-1].addTopCount(counter) | |
| origin[0].addBottomCountForCompilation(counter, self) | |
| } | |
| @osrExits = {} | |
| json["osrExits"].each { | |
| | subJson | | |
| osrExit = OSRExit.new(self, originStackFromJSON(subJson["origin"]), | |
| json["osrExitSites"][subJson["id"]].map { | |
| | value | | |
| value.hex | |
| }, subJson["exitKind"], subJson["isWatchpoint"], | |
| subJson["count"]) | |
| osrExit.codeAddresses.each { | |
| | codeAddress | | |
| osrExits[codeAddress] = [] unless osrExits[codeAddress] | |
| osrExits[codeAddress] << osrExit | |
| } | |
| osrExit.origin[-1].osrExits << osrExit | |
| } | |
| @profiledBytecodes = [] | |
| json["profiledBytecodes"].each { | |
| | subJson | | |
| @profiledBytecodes << ProfiledBytecodes.new(subJson) | |
| } | |
| @numInlinedGetByIds = json["numInlinedGetByIds"] | |
| @numInlinedPutByIds = json["numInlinedPutByIds"] | |
| @numInlinedCalls = json["numInlinedCalls"] | |
| end | |
| def counter(origin) | |
| @counters[origin] | |
| end | |
| def to_s | |
| "#{bytecode}-#{compilationIndex}-#{engine}" | |
| end | |
| end | |
| class DescriptionLine | |
| attr_reader :actualCountsString, :sourceCountsString, :disassembly, :shouldShow | |
| def initialize(actualCountsString, sourceCountsString, disassembly, shouldShow) | |
| @actualCountsString = actualCountsString | |
| @sourceCountsString = sourceCountsString | |
| @disassembly = disassembly | |
| @shouldShow = shouldShow | |
| end | |
| def codeAddress | |
| if @disassembly =~ /^\s*(0x[0-9a-fA-F]+):/ | |
| $1.hex | |
| else | |
| nil | |
| end | |
| end | |
| end | |
| if ARGV.length != 1 | |
| $stderr.puts "Usage: display-profiler-output <path to profiler output file>" | |
| $stderr.puts | |
| $stderr.puts "The typical usage pattern for the profiler currently looks something like:" | |
| $stderr.puts | |
| $stderr.puts "Path/To/jsc -p profile.json myprogram.js" | |
| $stderr.puts "display-profiler-output profile.json" | |
| exit 1 | |
| end | |
| $json = JSON::parse(IO::read(ARGV[0])) | |
| $bytecodes = $json["bytecodes"].map { | |
| | subJson | | |
| Bytecodes.new(subJson) | |
| } | |
| $compilations = $json["compilations"].map { | |
| | subJson | | |
| Compilation.new(subJson) | |
| } | |
| $engines = ["Baseline", "DFG"] | |
| def lpad(str,chars) | |
| if str.length>chars | |
| str | |
| else | |
| "%#{chars}s"%(str) | |
| end | |
| end | |
| def rpad(str, chars) | |
| while str.length < chars | |
| str += " " | |
| end | |
| str | |
| end | |
| def center(str, chars) | |
| while str.length < chars | |
| str += " " | |
| if str.length < chars | |
| str = " " + str | |
| end | |
| end | |
| str | |
| end | |
| def mayBeHash(hash) | |
| hash =~ /#/ or hash.size == 6 | |
| end | |
| def sourceOnOneLine(source, limit) | |
| source.gsub(/\s+/, ' ')[0...limit] | |
| end | |
| def screenWidth | |
| if $stdin.tty? | |
| HighLine::SystemExtensions.terminal_size[0] | |
| else | |
| 200 | |
| end | |
| end | |
| def summary(mode) | |
| remaining = screenWidth | |
| # Figure out how many columns we need for the code block names, and for counts | |
| maxCount = 0 | |
| maxName = 0 | |
| $bytecodes.each { | |
| | bytecodes | | |
| maxCount = ([maxCount] + $engines.map { | |
| | engine | | |
| bytecodes.maxTopExecutionCount(engine) | |
| } + $engines.map { | |
| | engine | | |
| bytecodes.maxBottomExecutionCount(engine) | |
| }).max | |
| maxName = [bytecodes.to_s.size, maxName].max | |
| } | |
| maxCountDigits = maxCount.to_s.size | |
| hashCols = [[maxName, 30].min, "CodeBlock".size].max | |
| remaining -= hashCols + 1 | |
| countCols = [maxCountDigits * $engines.size, "Source Counts".size].max | |
| remaining -= countCols + 1 | |
| if mode == :full | |
| instructionCountCols = 6 | |
| remaining -= instructionCountCols + 1 | |
| machineCountCols = [maxCountDigits * $engines.size, "Machine Counts".size].max | |
| remaining -= machineCountCols + 1 | |
| compilationsCols = 7 | |
| remaining -= compilationsCols + 1 | |
| inlinesCols = 9 | |
| remaining -= inlinesCols + 1 | |
| exitCountCols = 7 | |
| remaining -= exitCountCols + 1 | |
| recentOptsCols = 12 | |
| remaining -= recentOptsCols + 1 | |
| end | |
| if remaining > 0 | |
| sourceCols = remaining | |
| else | |
| sourceCols = nil | |
| end | |
| print(center("CodeBlock", hashCols)) | |
| if mode == :full | |
| print(" " + center("#Instr", instructionCountCols)) | |
| end | |
| print(" " + center("Source Counts", countCols)) | |
| if mode == :full | |
| print(" " + center("Machine Counts", machineCountCols)) | |
| print(" " + center("#Compil", compilationsCols)) | |
| print(" " + center("Inlines", inlinesCols)) | |
| print(" " + center("#Exits", exitCountCols)) | |
| print(" " + center("Last Opts", recentOptsCols)) | |
| end | |
| if sourceCols | |
| print(" " + center("Source", sourceCols)) | |
| end | |
| puts | |
| print(center("", hashCols)) | |
| if mode == :full | |
| print(" " + (" " * instructionCountCols)) | |
| end | |
| print(" " + center("Base/DFG", countCols)) | |
| if mode == :full | |
| print(" " + center("Base/DFG", machineCountCols)) | |
| print(" " + (" " * compilationsCols)) | |
| print(" " + center("Src/Total", inlinesCols)) | |
| print(" " + (" " * exitCountCols)) | |
| print(" " + center("Get/Put/Call", recentOptsCols)) | |
| end | |
| puts | |
| $bytecodes.sort { | |
| | a, b | | |
| b.totalMaxTopExecutionCount <=> a.totalMaxTopExecutionCount | |
| }.each { | |
| | bytecode | | |
| print(center(bytecode.name(hashCols), hashCols)) | |
| if mode == :full | |
| print(" " + center(bytecode.instructionCount.to_s, instructionCountCols)) | |
| end | |
| print(" " + | |
| center($engines.map { | |
| | engine | | |
| bytecode.maxTopExecutionCount(engine).to_s | |
| }.join("/"), countCols)) | |
| if mode == :full | |
| print(" " + center($engines.map { | |
| | engine | | |
| bytecode.maxBottomExecutionCount(engine).to_s | |
| }.join("/"), machineCountCols)) | |
| print(" " + center(bytecode.compilations.size.to_s, compilationsCols)) | |
| print(" " + center(bytecode.sourceMachineInlineSites.to_s + "/" + bytecode.totalMachineInlineSites.to_s, inlinesCols)) | |
| print(" " + center(bytecode.totalExitCount.to_s, exitCountCols)) | |
| lastCompilation = bytecode.compilations[-1] | |
| if lastCompilation | |
| optData = [lastCompilation.numInlinedGetByIds, | |
| lastCompilation.numInlinedPutByIds, | |
| lastCompilation.numInlinedCalls] | |
| else | |
| optData = ["N/A"] | |
| end | |
| print(" " + center(optData.join('/'), recentOptsCols)) | |
| end | |
| if sourceCols | |
| print(" " + sourceOnOneLine(bytecode.source, sourceCols)) | |
| end | |
| puts | |
| } | |
| end | |
| def executeCommand(*commandArray) | |
| command = commandArray[0] | |
| args = commandArray[1..-1] | |
| case command | |
| when "help", "h", "?" | |
| puts "summary (s) Print a summary of code block execution rates." | |
| puts "full (f) Same as summary, but prints more information." | |
| puts "source Show the source for a code block." | |
| puts "bytecode (b) Show the bytecode for a code block, with counts." | |
| puts "profiling (p) Show the (internal) profiling data for a code block." | |
| puts "display (d) Display details for a code block." | |
| puts "inlines Show all inlining stacks that the code block was on." | |
| puts "help (h) Print this message." | |
| puts "quit (q) Quit." | |
| when "quit", "q", "exit" | |
| exit 0 | |
| when "summary", "s" | |
| summary(:summary) | |
| when "full", "f" | |
| summary(:full) | |
| when "source" | |
| if args.length != 1 | |
| puts "Usage: source <code block hash>" | |
| return | |
| end | |
| $bytecodes.each { | |
| | bytecode | | |
| if bytecode.matches(args[0]) | |
| puts bytecode.source | |
| end | |
| } | |
| when "bytecode", "b" | |
| if args.length != 1 | |
| puts "Usage: source <code block hash>" | |
| return | |
| end | |
| hash = args[0] | |
| countCols = 10 * $engines.size | |
| machineCols = 10 * $engines.size | |
| pad = 1 | |
| while (countCols + 1 + machineCols + pad) % 8 != 0 | |
| pad += 1 | |
| end | |
| $bytecodes.each { | |
| | bytecodes | | |
| next unless bytecodes.matches(hash) | |
| puts(center("Source Counts", countCols) + " " + center("Machine Counts", machineCols) + | |
| (" " * pad) + center("Bytecode for #{bytecodes}", screenWidth - pad - countCols - 1 - machineCols)) | |
| puts(center("Base/DFG", countCols) + " " + center("Base/DFG", countCols)) | |
| bytecodes.each { | |
| | bytecode | | |
| if bytecode.shouldHaveCounts? | |
| countsString = $engines.map { | |
| | myEngine | | |
| bytecode.topExecutionCount(myEngine) | |
| }.join("/") | |
| machineString = $engines.map { | |
| | myEngine | | |
| bytecode.bottomExecutionCount(myEngine) | |
| }.join("/") | |
| else | |
| countsString = "" | |
| machineString = "" | |
| end | |
| puts(center(countsString, countCols) + " " + center(machineString, machineCols) + (" " * pad) + bytecode.description.chomp) | |
| bytecode.osrExits.each { | |
| | exit | | |
| puts(center("!!!!!", countCols) + " " + center("!!!!!", machineCols) + (" " * (pad + 10)) + | |
| "EXIT: in #{exit.compilation} due to #{exit.exitKind}, #{exit.count} times") | |
| } | |
| } | |
| } | |
| when "profiling", "p" | |
| if args.length != 1 | |
| puts "Usage: profiling <code block hash>" | |
| return | |
| end | |
| hash = args[0] | |
| first = true | |
| $compilations.each { | |
| | compilation | | |
| compilation.profiledBytecodes.each { | |
| | profiledBytecodes | | |
| if profiledBytecodes.bytecodes.matches(hash) | |
| if first | |
| first = false | |
| else | |
| puts | |
| end | |
| puts "Compilation #{compilation}:" | |
| profiledBytecodes.header.each { | |
| | header | | |
| puts(" " * 6 + header) | |
| } | |
| profiledBytecodes.each { | |
| | bytecode | | |
| puts(" " * 8 + bytecode.description) | |
| profiledBytecodes.bytecodes.bytecode(bytecode.bytecodeIndex).osrExits.each { | |
| | exit | | |
| if exit.compilation == compilation | |
| puts(" !!!!! EXIT: due to #{exit.exitKind}, #{exit.count} times") | |
| end | |
| } | |
| } | |
| end | |
| } | |
| } | |
| when "inlines" | |
| if args.length != 1 | |
| puts "Usage: inlines <code block hash>" | |
| return | |
| end | |
| hash = args[0] | |
| $bytecodes.each { | |
| | bytecodes | | |
| next unless bytecodes.matches(hash) | |
| # FIXME: print something useful to say more about which code block this is. | |
| $compilations.each { | |
| | compilation | | |
| myOrigins = [] | |
| compilation.descriptions.each { | |
| | description | | |
| if description.origin.index { | |
| | myBytecode | | |
| bytecodes == myBytecode.bytecodes | |
| } | |
| myOrigins << description.origin | |
| end | |
| } | |
| myOrigins.uniq! | |
| myOrigins.sort! { | |
| | a, b | | |
| result = 0 | |
| [a.size, b.size].min.times { | |
| | index | | |
| result = a[index].bytecodeIndex <=> b[index].bytecodeIndex | |
| break if result != 0 | |
| } | |
| result | |
| } | |
| next if myOrigins.empty? | |
| printArray = [] | |
| lastPrintStack = [] | |
| def originToPrintStack(origin) | |
| (0...(origin.size - 1)).map { | |
| | index | | |
| "bc\##{origin[index].bytecodeIndex} --> #{origin[index + 1].bytecodes}" | |
| } | |
| end | |
| def printStack(printArray, stack, lastStack) | |
| stillCommon = true | |
| stack.each_with_index { | |
| | entry, index | | |
| next if stillCommon and entry == lastStack[index] | |
| printArray << (" " * (index + 1) + entry) | |
| stillCommon = false | |
| } | |
| end | |
| myOrigins.each { | |
| | origin | | |
| currentPrintStack = originToPrintStack(origin) | |
| printStack(printArray, currentPrintStack, lastPrintStack) | |
| lastPrintStack = currentPrintStack | |
| } | |
| next if printArray.empty? | |
| puts "Compilation #{compilation}:" | |
| printArray.each { | |
| | entry | | |
| puts entry | |
| } | |
| } | |
| } | |
| when "display", "d" | |
| compilationIndex = nil | |
| case args.length | |
| when 1 | |
| if args[0] == "*" | |
| hash = nil | |
| else | |
| hash = args[0] | |
| end | |
| engine = nil | |
| when 2 | |
| if mayBeHash(args[0]) | |
| hash = args[0] | |
| engine = args[1] | |
| else | |
| engine = args[0] | |
| hash = args[1] | |
| end | |
| else | |
| puts "Usage: summary <code block hash> <engine>" | |
| return | |
| end | |
| if hash and hash =~ /-([0-9]+)-/ | |
| hash = $~.pre_match | |
| engine = $~.post_match | |
| compilationIndex = $1.to_i | |
| end | |
| if engine and not $engines.index(engine) | |
| pattern = Regexp.new(Regexp.escape(engine), "i") | |
| trueEngine = nil | |
| $engines.each { | |
| | myEngine | | |
| if myEngine =~ pattern | |
| trueEngine = myEngine | |
| break | |
| end | |
| } | |
| unless trueEngine | |
| puts "#{engine} is not a valid engine, try #{$engines.join(' or ')}." | |
| return | |
| end | |
| engine = trueEngine | |
| end | |
| actualCountCols = 13 | |
| sourceCountCols = 10 * $engines.size | |
| first = true | |
| $compilations.each { | |
| | compilation | | |
| next if hash and not compilation.bytecode.matches(hash) | |
| next if engine and compilation.engine != engine | |
| next if compilationIndex and compilation.compilationIndex != compilationIndex | |
| if first | |
| first = false | |
| else | |
| puts | |
| end | |
| puts("Compilation #{compilation}:") | |
| puts(" Num inlined: GetByIds: #{compilation.numInlinedGetByIds} PutByIds: #{compilation.numInlinedPutByIds} Calls: #{compilation.numInlinedCalls}") | |
| puts(center("Actual Counts", actualCountCols) + " " + center("Source Counts", sourceCountCols) + " " + center("Disassembly in #{compilation.engine}", screenWidth - 1 - sourceCountCols - 1 - actualCountCols)) | |
| puts((" " * actualCountCols) + " " + center("Base/DFG", sourceCountCols)) | |
| lines = [] | |
| compilation.descriptions.each { | |
| | description | | |
| # FIXME: We should have a better way of detecting things like CountExecution nodes | |
| # and slow path entries in the baseline JIT. | |
| if description.description =~ /CountExecution\(/ and compilation.engine == "DFG" | |
| shouldShow = false | |
| else | |
| shouldShow = true | |
| end | |
| if description.origin.empty? or not description.origin[-1].shouldHaveCounts? or (compilation.engine == "Baseline" and description.description =~ /^\s*\(S\)/) | |
| actualCountsString = "" | |
| sourceCountsString = "" | |
| else | |
| actualCountsString = compilation.counter(description.origin).count.to_s | |
| sourceCountsString = $engines.map { | |
| | myEngine | | |
| description.origin[-1].topExecutionCount(myEngine) | |
| }.join("/") | |
| end | |
| description.description.split("\n").each { | |
| | line | | |
| lines << DescriptionLine.new(actualCountsString, sourceCountsString, line.chomp, shouldShow) | |
| } | |
| } | |
| exitPrefix = center("!!!!!", actualCountCols) + " " + center("!!!!!", sourceCountCols) + (" " * 25) | |
| lines.each_with_index { | |
| | line, index | | |
| codeAddress = line.codeAddress | |
| if codeAddress | |
| list = compilation.osrExits[codeAddress] | |
| if list | |
| list.each { | |
| | exit | | |
| if exit.isWatchpoint | |
| exit.dumpForDisplay(exitPrefix) | |
| end | |
| } | |
| end | |
| end | |
| if line.shouldShow | |
| puts(center(line.actualCountsString, actualCountCols) + " " + center(line.sourceCountsString, sourceCountCols) + " " + line.disassembly) | |
| end | |
| if codeAddress | |
| # Find the next disassembly address. | |
| endIndex = index + 1 | |
| endAddress = nil | |
| while endIndex < lines.size | |
| myAddress = lines[endIndex].codeAddress | |
| if myAddress | |
| endAddress = myAddress | |
| break | |
| end | |
| endIndex += 1 | |
| end | |
| if endAddress | |
| list = compilation.osrExits[endAddress] | |
| if list | |
| list.each { | |
| | exit | | |
| unless exit.isWatchpoint | |
| exit.dumpForDisplay(exitPrefix) | |
| end | |
| } | |
| end | |
| end | |
| end | |
| } | |
| } | |
| else | |
| puts "Invalid command: #{command}" | |
| end | |
| end | |
| if $stdin.tty? | |
| executeCommand("full") | |
| end | |
| while commandLine = Readline.readline("> ", true) | |
| executeCommand(*commandLine.split) | |
| end | |