3 # Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
8 # 1. Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # 2. Redistributions in binary form must reproduce the above copyright
11 # notice, this list of conditions and the following disclaimer in the
12 # documentation and/or other materials provided with the distribution.
14 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 # THE POSSIBILITY OF SUCH DAMAGE.
34 $stderr.puts
"Error: some required gems are not installed!"
36 $stderr.puts
"Try running:"
38 $stderr.puts
"sudo gem install json"
39 $stderr.puts
"sudo gem install highline"
44 attr_accessor
:bytecodes, :bytecodeIndex, :opcode, :description, :topCounts, :bottomCounts, :machineInlinees, :osrExits
46 def initialize(bytecodes
, bytecodeIndex
, opcode
, description
)
47 @bytecodes = bytecodes
48 @bytecodeIndex = bytecodeIndex
50 @description = description
51 @topCounts = [] # "source" counts
52 @bottomCounts = {} # "machine" counts, maps compilations to counts
53 @machineInlinees = {} # maps my compilation to a set of inlinees
58 @opcode != "op_call_put_result"
61 def addTopCount(count
)
65 def addBottomCountForCompilation(count
, compilation
)
66 @bottomCounts[compilation
] = [] unless @bottomCounts[compilation
]
67 @bottomCounts[compilation
] << count
70 def addMachineInlinee(compilation
, inlinee
)
71 @machineInlinees[compilation
] = {} unless @machineInlinees[compilation
]
72 @machineInlinees[compilation
][inlinee
] = true
75 def totalTopExecutionCount
84 def topExecutionCount(engine
)
88 if value
.engine
== engine
95 def totalBottomExecutionCount
97 @bottomCounts.each_value
{
102 max
= [max
, value
.count
].max
109 def bottomExecutionCount(engine
)
111 @bottomCounts.each_pair
{
112 | compilation
, counts
|
113 if compilation
.engine
== engine
117 max
= [max
, value
.count
].max
136 attr_accessor
:codeHash, :inferredName, :source, :instructionCount, :machineInlineSites, :compilations
139 @codeHash = json
["hash"].to_s
140 @inferredName = json
["inferredName"].to_s
141 @source = json
["sourceCode"].to_s
142 @instructionCount = json
["instructionCount"].to_i
144 json
["bytecode"].each
{
146 index
= subJson
["bytecodeIndex"].to_i
147 @bytecode[index
] = Bytecode
.new(self, index
, subJson
["opcode"].to_s
, subJson
["description"].to_s
)
149 @machineInlineSites = {} # maps compilation to a set of origins
162 "#{@inferredName}\##{@codeHash}"
167 $~
.post_match
== @codeHash
171 pattern
== @inferredName or pattern
== @codeHash
176 @bytecode.values
.sort
{|a
, b
| a
.bytecodeIndex
<=> b
.bytecodeIndex
}.each
{
182 def bytecode(bytecodeIndex
)
183 @bytecode[bytecodeIndex
]
186 def addMachineInlineSite(compilation
, origin
)
187 @machineInlineSites[compilation
] = {} unless @machineInlineSites[compilation
]
188 @machineInlineSites[compilation
][origin
] = true
191 def totalMachineInlineSites
193 @machineInlineSites.each_value
{
200 def sourceMachineInlineSites
202 @machineInlineSites.each_value
{
209 def totalMaxTopExecutionCount
211 @bytecode.each_value
{
213 max
= [max
, bytecode
.totalTopExecutionCount
].max
218 def maxTopExecutionCount(engine
)
220 @bytecode.each_value
{
222 max
= [max
, bytecode
.topExecutionCount(engine
)].max
227 def totalMaxBottomExecutionCount
229 @bytecode.each_value
{
231 max
= [max
, bytecode
.totalBottomExecutionCount
].max
236 def maxBottomExecutionCount(engine
)
238 @bytecode.each_value
{
240 max
= [max
, bytecode
.bottomExecutionCount(engine
)].max
249 sum
+= bytecode
.totalExitCount
255 class ProfiledBytecode
256 attr_reader
:bytecodeIndex, :description
259 @bytecodeIndex = json
["bytecodeIndex"].to_i
260 @description = json
["description"].to_s
264 class ProfiledBytecodes
265 attr_reader
:header, :bytecodes
268 @header = json
["header"]
269 @bytecodes = $bytecodes[json
["bytecodesID"].to_i
]
270 @sequence = json
["bytecode"].map
{
272 ProfiledBytecode
.new(subJson
)
284 def originStackFromJSON(json
)
287 $bytecodes[subJson
["bytecodesID"].to_i
].bytecode(subJson
["bytecodeIndex"].to_i
)
291 class CompiledBytecode
292 attr_accessor
:origin, :description
295 @origin = originStackFromJSON(json
["origin"])
296 @description = json
["description"].to_s
300 class ExecutionCounter
301 attr_accessor
:origin, :engine, :count
303 def initialize(origin
, engine
, count
)
311 attr_reader
:compilation, :origin, :codeAddresses, :exitKind, :isWatchpoint, :count
313 def initialize(compilation
, origin
, codeAddresses
, exitKind
, isWatchpoint
, count
)
314 @compilation = compilation
316 @codeAddresses = codeAddresses
318 @isWatchpoint = isWatchpoint
322 def dumpForDisplay(prefix
)
323 puts(prefix
+ "EXIT: due to #{@exitKind}, #{@count} times")
328 attr_accessor
:bytecode, :engine, :descriptions, :counters, :compilationIndex
329 attr_accessor
:osrExits, :profiledBytecodes, :numInlinedGetByIds, :numInlinedPutByIds
330 attr_accessor
:numInlinedCalls
333 @bytecode = $bytecodes[json
["bytecodesID"].to_i
]
334 @bytecode.compilations
<< self
335 @compilationIndex = @bytecode.compilations
.size
336 @engine = json
["compilationKind"]
337 @descriptions = json
["descriptions"].map
{
339 CompiledBytecode
.new(subJson
)
343 next if description
.origin
.empty
?
344 description
.origin
[1..-1].each_with_index
{
346 description
.origin
[0].addMachineInlinee(self, inlinee
.bytecodes
)
347 inlinee
.bytecodes
.addMachineInlineSite(self, description
.origin
[0...index
])
351 json
["counters"].each
{
353 origin
= originStackFromJSON(subJson
["origin"])
354 counter
= ExecutionCounter
.new(origin
, @engine, subJson
["executionCount"].to_i
)
355 @counters[origin
] = counter
356 origin
[-1].addTopCount(counter
)
357 origin
[0].addBottomCountForCompilation(counter
, self)
360 json
["osrExits"].each
{
362 osrExit
= OSRExit
.new(self, originStackFromJSON(subJson
["origin"]),
363 json
["osrExitSites"][subJson
["id"]].map
{
366 }, subJson
["exitKind"], subJson
["isWatchpoint"],
368 osrExit
.codeAddresses
.each
{
370 osrExits
[codeAddress
] = [] unless osrExits
[codeAddress
]
371 osrExits
[codeAddress
] << osrExit
373 osrExit
.origin
[-1].osrExits
<< osrExit
375 @profiledBytecodes = []
376 json
["profiledBytecodes"].each
{
378 @profiledBytecodes << ProfiledBytecodes
.new(subJson
)
380 @numInlinedGetByIds = json
["numInlinedGetByIds"]
381 @numInlinedPutByIds = json
["numInlinedPutByIds"]
382 @numInlinedCalls = json
["numInlinedCalls"]
390 "#{bytecode}-#{compilationIndex}-#{engine}"
394 class DescriptionLine
395 attr_reader
:actualCountsString, :sourceCountsString, :disassembly, :shouldShow
397 def initialize(actualCountsString
, sourceCountsString
, disassembly
, shouldShow
)
398 @actualCountsString = actualCountsString
399 @sourceCountsString = sourceCountsString
400 @disassembly = disassembly
401 @shouldShow = shouldShow
405 if @disassembly =~
/^\s*(0x[0-9a-fA-F]+):/
414 $stderr.puts
"Usage: display-profiler-output <path to profiler output file>"
416 $stderr.puts
"The typical usage pattern for the profiler currently looks something like:"
418 $stderr.puts
"Path/To/jsc -p profile.json myprogram.js"
419 $stderr.puts
"display-profiler-output profile.json"
423 $json = JSON
::parse(IO
::read(ARGV[0]))
424 $bytecodes = $json["bytecodes"].map
{
426 Bytecodes
.new(subJson
)
428 $compilations = $json["compilations"].map
{
430 Compilation
.new(subJson
)
432 $engines = ["Baseline", "DFG"]
443 while str.length < chars
449 def center(str, chars)
450 while str.length < chars
452 if str.length < chars
460 hash =~ /#/ or hash.size == 6
463 def sourceOnOneLine(source, limit)
464 source.gsub(/\s+/, ' ')[0...limit]
469 HighLine::SystemExtensions.terminal_size[0]
476 remaining = screenWidth
478 # Figure out how many columns we need for the code block names, and for counts
483 maxCount = ([maxCount] + $engines.map {
485 bytecodes.maxTopExecutionCount(engine)
488 bytecodes.maxBottomExecutionCount(engine)
490 maxName = [bytecodes.to_s.size, maxName].max
492 maxCountDigits = maxCount.to_s.size
494 hashCols = [[maxName, 30].min, "CodeBlock".size].max
495 remaining -= hashCols + 1
497 countCols = [maxCountDigits * $engines.size, "Source Counts".size].max
498 remaining -= countCols + 1
501 instructionCountCols = 6
502 remaining -= instructionCountCols + 1
504 machineCountCols = [maxCountDigits * $engines.size, "Machine Counts".size].max
505 remaining -= machineCountCols + 1
508 remaining -= compilationsCols + 1
511 remaining -= inlinesCols + 1
514 remaining -= exitCountCols + 1
517 remaining -= recentOptsCols + 1
521 sourceCols = remaining
526 print(center("CodeBlock", hashCols))
528 print(" " + center("#Instr", instructionCountCols))
530 print(" " + center("Source Counts", countCols))
532 print(" " + center("Machine Counts", machineCountCols))
533 print(" " + center("#Compil", compilationsCols))
534 print(" " + center("Inlines", inlinesCols))
535 print(" " + center("#Exits", exitCountCols))
536 print(" " + center("Last Opts", recentOptsCols))
539 print(" " + center("Source", sourceCols))
543 print(center("", hashCols))
545 print(" " + (" " * instructionCountCols))
547 print(" " + center("Base/DFG", countCols))
549 print(" " + center("Base/DFG", machineCountCols))
550 print(" " + (" " * compilationsCols))
551 print(" " + center("Src/Total", inlinesCols))
552 print(" " + (" " * exitCountCols))
553 print(" " + center("Get/Put/Call", recentOptsCols))
558 b.totalMaxTopExecutionCount <=> a.totalMaxTopExecutionCount
561 print(center(bytecode.name(hashCols), hashCols))
563 print(" " + center(bytecode.instructionCount.to_s, instructionCountCols))
566 center($engines.map {
568 bytecode.maxTopExecutionCount(engine).to_s
569 }.join("/"), countCols))
571 print(" " + center($engines.map {
573 bytecode.maxBottomExecutionCount(engine).to_s
574 }.join("/"), machineCountCols))
575 print(" " + center(bytecode.compilations.size.to_s, compilationsCols))
576 print(" " + center(bytecode.sourceMachineInlineSites.to_s + "/" + bytecode.totalMachineInlineSites.to_s, inlinesCols))
577 print(" " + center(bytecode.totalExitCount.to_s, exitCountCols))
578 lastCompilation = bytecode.compilations[-1]
580 optData = [lastCompilation.numInlinedGetByIds,
581 lastCompilation.numInlinedPutByIds,
582 lastCompilation.numInlinedCalls]
586 print(" " + center(optData.join('/'), recentOptsCols))
589 print(" " + sourceOnOneLine(bytecode.source, sourceCols))
595 def executeCommand(*commandArray)
596 command = commandArray[0]
597 args = commandArray[1..-1]
599 when "help", "h", "?"
600 puts "summary (s) Print a summary of code block execution rates."
601 puts "full (f) Same as summary, but prints more information."
602 puts "source Show the source for a code block."
603 puts "bytecode (b) Show the bytecode for a code block, with counts."
604 puts "profiling (p) Show the (internal) profiling data for a code block."
605 puts "display (d) Display details for a code block."
606 puts "inlines Show all inlining stacks that the code block was on."
607 puts "help (h) Print this message."
608 puts "quit (q) Quit."
609 when "quit", "q", "exit"
617 puts "Usage: source <code block hash>"
622 if bytecode.matches(args[0])
628 puts "Usage: source <code block hash>"
634 countCols = 10 * $engines.size
635 machineCols = 10 * $engines.size
637 while (countCols + 1 + machineCols + pad) % 8 != 0
643 next unless bytecodes.matches(hash)
644 puts(center("Source Counts", countCols) + " " + center("Machine Counts", machineCols) +
645 (" " * pad) + center("Bytecode for #{bytecodes}", screenWidth - pad - countCols - 1 - machineCols))
646 puts(center("Base/DFG", countCols) + " " + center("Base/DFG", countCols))
649 if bytecode.shouldHaveCounts?
650 countsString = $engines.map {
652 bytecode.topExecutionCount(myEngine)
654 machineString = $engines.map {
656 bytecode.bottomExecutionCount(myEngine)
662 puts(center(countsString, countCols) + " " + center(machineString, machineCols) + (" " * pad) + bytecode.description.chomp)
663 bytecode.osrExits.each {
665 puts(center("!!!!!", countCols) + " " + center("!!!!!", machineCols) + (" " * (pad + 10)) +
666 "EXIT: in #{exit.compilation} due to #{exit.exitKind}, #{exit.count} times")
670 when "profiling", "p"
672 puts "Usage: profiling <code block hash>"
682 compilation.profiledBytecodes.each {
683 | profiledBytecodes |
684 if profiledBytecodes.bytecodes.matches(hash)
691 puts "Compilation #{compilation}:"
692 profiledBytecodes.header.each {
694 puts(" " * 6 + header)
696 profiledBytecodes.each {
698 puts(" " * 8 + bytecode.description)
699 profiledBytecodes.bytecodes.bytecode(bytecode.bytecodeIndex).osrExits.each {
701 if exit.compilation == compilation
702 puts(" !!!!! EXIT: due to #{exit.exitKind}, #{exit.count} times")
711 puts "Usage: inlines <code block hash>"
719 next unless bytecodes.matches(hash)
721 # FIXME: print something useful to say more about which code block this is.
726 compilation.descriptions.each {
728 if description.origin.index {
730 bytecodes == myBytecode.bytecodes
732 myOrigins << description.origin
739 [a.size, b.size].min.times {
741 result = a[index].bytecodeIndex <=> b[index].bytecodeIndex
747 next if myOrigins.empty?
752 def originToPrintStack(origin)
753 (0...(origin.size - 1)).map {
755 "bc\##{origin[index].bytecodeIndex} --> #{origin[index + 1].bytecodes}"
759 def printStack(printArray, stack, lastStack)
761 stack.each_with_index {
763 next if stillCommon and entry == lastStack[index]
764 printArray << (" " * (index + 1) + entry)
771 currentPrintStack = originToPrintStack(origin)
772 printStack(printArray, currentPrintStack, lastPrintStack)
773 lastPrintStack = currentPrintStack
776 next if printArray.empty?
778 puts "Compilation #{compilation}:"
786 compilationIndex = nil
797 if mayBeHash(args[0])
805 puts "Usage: summary <code block hash> <engine>"
809 if hash and hash =~ /-([0-9]+)-/
811 engine = $~.post_match
812 compilationIndex = $1.to_i
815 if engine and not $engines.index(engine)
816 pattern = Regexp.new(Regexp.escape(engine), "i")
820 if myEngine =~ pattern
821 trueEngine = myEngine
826 puts "#{engine} is not a valid engine, try #{$engines.join(' or ')}."
833 sourceCountCols = 10 * $engines.size
838 next if hash and not compilation.bytecode.matches(hash)
839 next if engine and compilation.engine != engine
840 next if compilationIndex and compilation.compilationIndex != compilationIndex
848 puts("Compilation #{compilation}:")
849 puts(" Num inlined: GetByIds: #{compilation.numInlinedGetByIds} PutByIds: #{compilation.numInlinedPutByIds} Calls: #{compilation.numInlinedCalls}")
850 puts(center("Actual Counts", actualCountCols) + " " + center("Source Counts", sourceCountCols) + " " + center("Disassembly in #{compilation.engine}", screenWidth - 1 - sourceCountCols - 1 - actualCountCols))
851 puts((" " * actualCountCols) + " " + center("Base/DFG", sourceCountCols))
855 compilation.descriptions.each {
857 # FIXME: We should have a better way of detecting things like CountExecution nodes
858 # and slow path entries in the baseline JIT.
859 if description.description =~ /CountExecution\(/ and compilation.engine == "DFG"
864 if description.origin.empty? or not description.origin[-1].shouldHaveCounts? or (compilation.engine == "Baseline" and description.description =~ /^\s*\(S\)/)
865 actualCountsString = ""
866 sourceCountsString = ""
868 actualCountsString = compilation.counter(description.origin).count.to_s
869 sourceCountsString = $engines.map {
871 description.origin[-1].topExecutionCount(myEngine)
874 description.description.split("\n").each {
876 lines << DescriptionLine.new(actualCountsString, sourceCountsString, line.chomp, shouldShow)
880 exitPrefix = center("!!!!!", actualCountCols) + " " + center("!!!!!", sourceCountCols) + (" " * 25)
882 lines.each_with_index {
884 codeAddress = line.codeAddress
886 list = compilation.osrExits[codeAddress]
891 exit.dumpForDisplay(exitPrefix)
897 puts(center(line.actualCountsString, actualCountCols) + " " + center(line.sourceCountsString, sourceCountCols) + " " + line.disassembly)
900 # Find the next disassembly address.
903 while endIndex < lines.size
904 myAddress = lines[endIndex].codeAddress
906 endAddress = myAddress
913 list = compilation.osrExits[endAddress]
917 unless exit.isWatchpoint
918 exit.dumpForDisplay(exitPrefix)
927 puts "Invalid command: #{command}"
932 executeCommand("full")
935 while commandLine = Readline.readline("> ", true)
936 executeCommand(*commandLine.split)