| #!/usr/bin/env ruby |
| |
| # Copyright (C) 2012-2014, 2016 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 |
| |
| def codeHashSortKey |
| codeHash |
| 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, :jettisonReason, :additionalJettisonReason, :uid |
| |
| 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"] |
| @jettisonReason = json["jettisonReason"] |
| @additionalJettisonReason = json["additionalJettisonReason"] |
| @uid = json["uid"] |
| end |
| |
| def codeHashSortKey |
| bytecode.codeHashSortKey + "-" + compilationIndex.to_s |
| end |
| |
| def counter(origin) |
| @counters[origin] |
| end |
| |
| def totalCount |
| sum = 0 |
| @counters.values.each { |
| | value | |
| sum += value.count |
| } |
| sum |
| end |
| |
| def maxCount |
| max = 0 |
| @counters.values.each { |
| | value | |
| max = [max, value.count].max |
| } |
| max |
| 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 |
| |
| class Event |
| attr_reader :time, :bytecode, :compilation, :summary, :detail |
| |
| def initialize(json) |
| @time = json["time"].to_f |
| @bytecode = $bytecodes[json["bytecodesID"].to_i] |
| if json["compilationUID"] |
| @compilation = $compilationMap[json["compilationUID"]] |
| end |
| @summary = json["summary"] |
| @detail = json["detail"] |
| end |
| end |
| |
| def originToPrintStack(origin) |
| (0...(origin.size - 1)).map { |
| | index | |
| "bc\##{origin[index].bytecodeIndex} --> #{origin[index + 1].bytecodes}" |
| } |
| end |
| |
| def originToString(origin) |
| (originToPrintStack(origin) + ["bc\##{origin[-1].bytecodeIndex}"]).join(" ") |
| 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) |
| } |
| $compilationMap = {} |
| $compilations.each { |
| | compilation | |
| $compilationMap[compilation.uid] = compilation |
| } |
| $events = $json["events"].map { |
| | subJson | |
| Event.new(subJson) |
| } |
| $engines = ["Baseline", "DFG", "FTL", "FTLForOSREntry"] |
| |
| def isOptimizing(engine) |
| engine == "DFG" or engine == "FTL" or engine == "FTLForOSREntry" |
| end |
| |
| $showCounts = true |
| $sortMode = :time |
| |
| def lpad(str, chars) |
| str = str.to_s |
| if str.length > chars |
| str |
| else |
| "%#{chars}s"%(str) |
| end |
| end |
| |
| def rpad(str, chars) |
| str = str.to_s |
| while str.length < chars |
| str += " " |
| end |
| str |
| end |
| |
| def center(str, chars) |
| str = str.to_s |
| 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? |
| begin |
| HighLine::SystemExtensions.terminal_size[0] - 3 |
| rescue |
| HighLine::default_instance.terminal.terminal_size[0] - 3 |
| end |
| else |
| 200 |
| end |
| end |
| |
| def sortByMode(list) |
| if list.size == 1 |
| return list |
| end |
| case $sortMode |
| when :time |
| list |
| when :hash |
| puts "Will sort output by code hash instead of compilation time." |
| puts "Use 'sort time' to change back to the default." |
| puts |
| list.sort { | a, b | a.codeHashSortKey <=> b.codeHashSortKey } |
| else |
| raise |
| end |
| end |
| |
| def summary(mode, order) |
| 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 + ($engines.size - 1), "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/FTL/FTLOSR", countCols)) |
| if mode == :full |
| print(" " + center("Base/DFG/FTL/FTLOSR", machineCountCols)) |
| print(" " + (" " * compilationsCols)) |
| print(" " + center("Src/Total", inlinesCols)) |
| print(" " + (" " * exitCountCols)) |
| print(" " + center("Get/Put/Call", recentOptsCols)) |
| end |
| puts |
| $bytecodes.sort { |
| | a, b | |
| case order |
| when :bytecode |
| b.totalMaxTopExecutionCount <=> a.totalMaxTopExecutionCount |
| when :machine |
| b.totalMaxBottomExecutionCount <=> a.totalMaxBottomExecutionCount |
| when :exits |
| b.totalExitCount <=> a.totalExitCount |
| when :compiles |
| b.compilations.size <=> a.compilations.size |
| else |
| raise |
| end |
| }.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 queryCompilations(command, args) |
| compilationIndex = nil |
| |
| case args.length |
| when 1 |
| hash = args[0] |
| 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: #{command} <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 |
| |
| if hash == "*" |
| hash = nil |
| end |
| |
| sortByMode($compilations).each { |
| | compilation | |
| next if hash and not compilation.bytecode.matches(hash) |
| next if engine and compilation.engine != engine |
| if compilationIndex |
| if compilationIndex < 0 |
| next unless compilation.bytecode.compilations[compilationIndex] == compilation |
| else |
| next unless compilation.compilationIndex == compilationIndex |
| end |
| end |
| |
| yield compilation |
| } |
| 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 "log (l) List the compilations, exits, and jettisons involving this code block." |
| puts "events (e) List of events involving this code block." |
| puts "display (d) Display details for a code block." |
| puts "inlines Show all inlining stacks that the code block was on." |
| puts "counts Set whether to show counts for 'bytecode' and 'display'." |
| puts "sort Set how to sort compilations before display." |
| puts "help (h) Print this message." |
| puts "quit (q) Quit." |
| when "quit", "q", "exit" |
| exit 0 |
| when "summary", "s" |
| summary(:summary, :bytecode) |
| when "full", "f" |
| if args[0] and (args[0] == "m" or args[0] == "machine") |
| summary(:full, :machine) |
| elsif args[0] and (args[0] == "e" or args[0] == "exits") |
| summary(:full, :exits) |
| elsif args[0] and (args[0] == "c" or args[0] == "compiles") |
| summary(:full, :compiles) |
| else |
| summary(:full, :bytecode) |
| end |
| 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 |
| |
| sortByMode($bytecodes).each { |
| | bytecodes | |
| next unless bytecodes.matches(hash) |
| if $showCounts |
| puts(center("Source Counts", countCols) + " " + center("Machine Counts", machineCols) + |
| (" " * pad) + center("Bytecode for #{bytecodes}", screenWidth - pad - countCols - 1 - machineCols)) |
| puts(center("Base/DFG/FTL/FTLOSR", countCols) + " " + center("Base/DFG/FTL/FTLOSR", countCols)) |
| else |
| puts("Bytecode for #{bytecodes}:") |
| end |
| bytecodes.each { |
| | bytecode | |
| if $showCounts |
| if bytecode.shouldHaveCounts? |
| countsString = $engines.map { |
| | myEngine | |
| bytecode.topExecutionCount(myEngine) |
| }.join("/") |
| machineString = $engines.map { |
| | myEngine | |
| bytecode.bottomExecutionCount(myEngine) |
| }.join("/") |
| else |
| countsString = "" |
| machineString = "" |
| end |
| print(center(countsString, countCols) + " " + center(machineString, machineCols) + (" " * pad)) |
| end |
| puts(bytecode.description.chomp) |
| bytecode.osrExits.each { |
| | exit | |
| if $showCounts |
| print(center("!!!!!", countCols) + " " + center("!!!!!", machineCols) + (" " * pad)) |
| end |
| print(" " * 10) |
| puts("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 |
| sortByMode($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 "log", "l" |
| queryCompilations("log", args) { |
| | compilation | |
| puts "Compilation #{compilation}:" |
| puts " Total count: #{compilation.totalCount} Max count: #{compilation.maxCount}" |
| compilation.osrExits.values.each { |
| | exits | |
| exits.each { |
| | exit | |
| puts " EXIT: at #{originToString(exit.origin)} due to #{exit.exitKind}, #{exit.count} times" |
| } |
| } |
| if compilation.jettisonReason != "NotJettisoned" |
| puts " Jettisoned due to #{compilation.jettisonReason}" |
| if compilation.additionalJettisonReason |
| puts " #{compilation.additionalJettisonReason}" |
| end |
| end |
| } |
| when "events", "e" |
| if args.length != 1 |
| puts "Usage: inlines <code block hash>" |
| return |
| end |
| |
| hash = Regexp.new(Regexp.escape(args[0])) |
| |
| events = [] |
| $events.each { |
| | event | |
| if event.bytecode.to_s =~ hash |
| events << event |
| end |
| } |
| |
| timeCols = 0 |
| hashCols = 0 |
| compilationCols = 0 |
| summaryCols = 0 |
| events.each { |
| | event | |
| timeCols = [event.time.to_s.size, timeCols].max |
| hashCols = [event.bytecode.to_s.size, hashCols].max |
| if event.compilation |
| compilationCols = [event.compilation.to_s.size, compilationCols].max |
| end |
| summaryCols = [event.summary.size, summaryCols].max |
| } |
| |
| events.each { |
| | event | |
| print rpad(event.time.to_s, timeCols) |
| print " " |
| print rpad(event.bytecode.to_s, hashCols) |
| print " " |
| compilationStr = "" |
| if event.compilation |
| compilationStr = event.compilation.to_s |
| end |
| print rpad(compilationStr, compilationCols) |
| print " " |
| print rpad(event.summary, summaryCols) |
| print " " |
| puts event.detail |
| } |
| when "inlines" |
| if args.length != 1 |
| puts "Usage: inlines <code block hash>" |
| return |
| end |
| |
| hash = args[0] |
| |
| sortByMode($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 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" |
| actualCountCols = 13 |
| sourceCountCols = 10 * $engines.size |
| |
| first = true |
| |
| queryCompilations("display", args) { |
| | compilation | |
| |
| if first |
| first = false |
| else |
| puts |
| end |
| |
| puts("Compilation #{compilation}:") |
| if $showCounts |
| puts(center("Actual Counts", actualCountCols) + " " + center("Source Counts", sourceCountCols) + " " + center("Disassembly in #{compilation.engine}", screenWidth - 1 - sourceCountCols - 1 - actualCountCols)) |
| puts((" " * actualCountCols) + " " + center("Base/DFG/FTL/FTLOSR", sourceCountCols)) |
| else |
| puts("Disassembly in #{compilation.engine}") |
| end |
| |
| 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 isOptimizing(compilation.engine) |
| shouldShow = false |
| else |
| shouldShow = true |
| end |
| if not description.origin.empty? and not description.origin[-1] |
| p description.origin |
| p description.description |
| 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 = "" |
| if $showCounts |
| exitPrefix += center("!!!!!", actualCountCols) + " " + center("!!!!!", sourceCountCols) + (" " * 15) |
| else |
| exitPrefix += " !!!!!" |
| end |
| exitPrefix += " " * 10 |
| |
| 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 |
| if $showCounts |
| print(center(line.actualCountsString, actualCountCols) + " " + center(line.sourceCountsString, sourceCountCols) + " ") |
| end |
| puts(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 |
| } |
| } |
| when "counts" |
| if args.length != 1 |
| puts "Usage: counts on|off|toggle" |
| else |
| case args[0].downcase |
| when 'on' |
| $showCounts = true |
| when 'off' |
| $showCounts = false |
| when 'toggle' |
| $showCounts = !$showCounts |
| else |
| puts "Usage: counts on|off|toggle" |
| end |
| end |
| puts "Current value: #{$showCounts ? 'on' : 'off'}" |
| when "sort" |
| if args.length != 1 |
| puts "Usage: sort time|hash" |
| puts |
| puts "sort time: Sorts by the timestamp of when the code was compiled." |
| puts " This is the default." |
| puts |
| puts "sort hash: Sorts by the code hash. This is more deterministic," |
| puts " and is useful for diffs." |
| puts |
| else |
| case args[0].downcase |
| when 'time' |
| $sortMode = :time |
| when 'hash' |
| $sortMode = :hash |
| else |
| puts "Usage: sort time|hash" |
| end |
| end |
| puts "Current value: #{$sortMode}" |
| else |
| puts "Invalid command: #{command}" |
| end |
| end |
| |
| if $stdin.tty? |
| executeCommand("full") |
| end |
| |
| while commandLine = Readline.readline("> ", true) |
| executeCommand(*commandLine.split) |
| end |
| |