display-profiler-output should be able to display code blocks sorted by machine counts
[WebKit-https.git] / Tools / Scripts / display-profiler-output
1 #!/usr/bin/env ruby
2
3 # Copyright (C) 2012, 2013, 2014 Apple Inc. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
7 # are met:
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.
13 #
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.
25
26 require 'rubygems'
27
28 require 'readline'
29
30 begin
31     require 'json'
32     require 'highline'
33 rescue LoadError
34     $stderr.puts "Error: some required gems are not installed!"
35     $stderr.puts
36     $stderr.puts "Try running:"
37     $stderr.puts
38     $stderr.puts "sudo gem install json"
39     $stderr.puts "sudo gem install highline"
40     exit 1
41 end
42
43 class Bytecode
44     attr_accessor :bytecodes, :bytecodeIndex, :opcode, :description, :topCounts, :bottomCounts, :machineInlinees, :osrExits
45     
46     def initialize(bytecodes, bytecodeIndex, opcode, description)
47         @bytecodes = bytecodes
48         @bytecodeIndex = bytecodeIndex
49         @opcode = opcode
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
54         @osrExits = []
55     end
56     
57     def shouldHaveCounts?
58         @opcode != "op_call_put_result"
59     end
60     
61     def addTopCount(count)
62         @topCounts << count
63     end
64     
65     def addBottomCountForCompilation(count, compilation)
66         @bottomCounts[compilation] = [] unless @bottomCounts[compilation]
67         @bottomCounts[compilation] << count
68     end
69     
70     def addMachineInlinee(compilation, inlinee)
71         @machineInlinees[compilation] = {} unless @machineInlinees[compilation]
72         @machineInlinees[compilation][inlinee] = true
73     end
74     
75     def totalTopExecutionCount
76         sum = 0
77         @topCounts.each {
78             | value |
79             sum += value.count
80         }
81         sum
82     end
83     
84     def topExecutionCount(engine)
85         sum = 0
86         @topCounts.each {
87             | value |
88             if value.engine == engine
89                 sum += value.count
90             end
91         }
92         sum
93     end
94     
95     def totalBottomExecutionCount
96         sum = 0
97         @bottomCounts.each_value {
98             | counts |
99             max = 0
100             counts.each {
101                 | value |
102                 max = [max, value.count].max
103             }
104             sum += max
105         }
106         sum
107     end
108     
109     def bottomExecutionCount(engine)
110         sum = 0
111         @bottomCounts.each_pair {
112             | compilation, counts |
113             if compilation.engine == engine
114                 max = 0
115                 counts.each {
116                     | value |
117                     max = [max, value.count].max
118                 }
119                 sum += max
120             end
121         }
122         sum
123     end
124     
125     def totalExitCount
126         sum = 0
127         @osrExits.each {
128             | exit |
129             sum += exit.count
130         }
131         sum
132     end
133 end
134
135 class Bytecodes
136     attr_accessor :codeHash, :inferredName, :source, :instructionCount, :machineInlineSites, :compilations
137     
138     def initialize(json)
139         @codeHash = json["hash"].to_s
140         @inferredName = json["inferredName"].to_s
141         @source = json["sourceCode"].to_s
142         @instructionCount = json["instructionCount"].to_i
143         @bytecode = {}
144         json["bytecode"].each {
145             | subJson |
146             index = subJson["bytecodeIndex"].to_i
147             @bytecode[index] = Bytecode.new(self, index, subJson["opcode"].to_s, subJson["description"].to_s)
148         }
149         @machineInlineSites = {} # maps compilation to a set of origins
150         @compilations = []
151     end
152     
153     def name(limit)
154         if to_s.size > limit
155             "\##{@codeHash}"
156         else
157             to_s
158         end
159     end
160     
161     def to_s
162         "#{@inferredName}\##{@codeHash}"
163     end
164     
165     def matches(pattern)
166         if pattern =~ /^#/
167             $~.post_match == @codeHash
168         elsif pattern =~ /#/
169             pattern == to_s
170         else
171             pattern == @inferredName or pattern == @codeHash
172         end
173     end
174     
175     def each
176         @bytecode.values.sort{|a, b| a.bytecodeIndex <=> b.bytecodeIndex}.each {
177             | value |
178             yield value
179         }
180     end
181     
182     def bytecode(bytecodeIndex)
183         @bytecode[bytecodeIndex]
184     end
185     
186     def addMachineInlineSite(compilation, origin)
187         @machineInlineSites[compilation] = {} unless @machineInlineSites[compilation]
188         @machineInlineSites[compilation][origin] = true
189     end
190     
191     def totalMachineInlineSites
192         sum = 0
193         @machineInlineSites.each_value {
194             | set |
195             sum += set.size
196         }
197         sum
198     end
199     
200     def sourceMachineInlineSites
201         set = {}
202         @machineInlineSites.each_value {
203             | mySet |
204             set.merge!(mySet)
205         }
206         set.size
207     end
208     
209     def totalMaxTopExecutionCount
210         max = 0
211         @bytecode.each_value {
212             | bytecode |
213             max = [max, bytecode.totalTopExecutionCount].max
214         }
215         max
216     end
217     
218     def maxTopExecutionCount(engine)
219         max = 0
220         @bytecode.each_value {
221             | bytecode |
222             max = [max, bytecode.topExecutionCount(engine)].max
223         }
224         max
225     end
226     
227     def totalMaxBottomExecutionCount
228         max = 0
229         @bytecode.each_value {
230             | bytecode |
231             max = [max, bytecode.totalBottomExecutionCount].max
232         }
233         max
234     end
235     
236     def maxBottomExecutionCount(engine)
237         max = 0
238         @bytecode.each_value {
239             | bytecode |
240             max = [max, bytecode.bottomExecutionCount(engine)].max
241         }
242         max
243     end
244     
245     def totalExitCount
246         sum = 0
247         each {
248             | bytecode |
249             sum += bytecode.totalExitCount
250         }
251         sum
252     end
253     
254     def codeHashSortKey
255         codeHash
256     end
257 end
258
259 class ProfiledBytecode
260     attr_reader :bytecodeIndex, :description
261     
262     def initialize(json)
263         @bytecodeIndex = json["bytecodeIndex"].to_i
264         @description = json["description"].to_s
265     end
266 end
267
268 class ProfiledBytecodes
269     attr_reader :header, :bytecodes
270     
271     def initialize(json)
272         @header = json["header"]
273         @bytecodes = $bytecodes[json["bytecodesID"].to_i]
274         @sequence = json["bytecode"].map {
275             | subJson |
276             ProfiledBytecode.new(subJson)
277         }
278     end
279     
280     def each
281         @sequence.each {
282             | description |
283             yield description
284         }
285     end
286 end
287
288 def originStackFromJSON(json)
289     json.map {
290         | subJson |
291         $bytecodes[subJson["bytecodesID"].to_i].bytecode(subJson["bytecodeIndex"].to_i)
292     }
293 end
294
295 class CompiledBytecode
296     attr_accessor :origin, :description
297     
298     def initialize(json)
299         @origin = originStackFromJSON(json["origin"])
300         @description = json["description"].to_s
301     end
302 end
303
304 class ExecutionCounter
305     attr_accessor :origin, :engine, :count
306     
307     def initialize(origin, engine, count)
308         @origin = origin
309         @engine = engine
310         @count = count
311     end
312 end
313
314 class OSRExit
315     attr_reader :compilation, :origin, :codeAddresses, :exitKind, :isWatchpoint, :count
316     
317     def initialize(compilation, origin, codeAddresses, exitKind, isWatchpoint, count)
318         @compilation = compilation
319         @origin = origin
320         @codeAddresses = codeAddresses
321         @exitKind = exitKind
322         @isWatchpoint = isWatchpoint
323         @count = count
324     end
325     
326     def dumpForDisplay(prefix)
327         puts(prefix + "EXIT: due to #{@exitKind}, #{@count} times")
328     end
329 end
330
331 class Compilation
332     attr_accessor :bytecode, :engine, :descriptions, :counters, :compilationIndex
333     attr_accessor :osrExits, :profiledBytecodes, :numInlinedGetByIds, :numInlinedPutByIds
334     attr_accessor :numInlinedCalls, :jettisonReason, :additionalJettisonReason
335     
336     def initialize(json)
337         @bytecode = $bytecodes[json["bytecodesID"].to_i]
338         @bytecode.compilations << self
339         @compilationIndex = @bytecode.compilations.size
340         @engine = json["compilationKind"]
341         @descriptions = json["descriptions"].map {
342             | subJson |
343             CompiledBytecode.new(subJson)
344         }
345         @descriptions.each {
346             | description |
347             next if description.origin.empty?
348             description.origin[1..-1].each_with_index {
349                 | inlinee, index |
350                 description.origin[0].addMachineInlinee(self, inlinee.bytecodes)
351                 inlinee.bytecodes.addMachineInlineSite(self, description.origin[0...index])
352             }
353         }
354         @counters = {}
355         json["counters"].each {
356             | subJson |
357             origin = originStackFromJSON(subJson["origin"])
358             counter = ExecutionCounter.new(origin, @engine, subJson["executionCount"].to_i)
359             @counters[origin] = counter
360             origin[-1].addTopCount(counter)
361             origin[0].addBottomCountForCompilation(counter, self)
362         }
363         @osrExits = {}
364         json["osrExits"].each {
365             | subJson |
366             osrExit = OSRExit.new(self, originStackFromJSON(subJson["origin"]),
367                                   json["osrExitSites"][subJson["id"]].map {
368                                       | value |
369                                       value.hex
370                                   }, subJson["exitKind"], subJson["isWatchpoint"],
371                                   subJson["count"])
372             osrExit.codeAddresses.each {
373                 | codeAddress |
374                 osrExits[codeAddress] = [] unless osrExits[codeAddress]
375                 osrExits[codeAddress] << osrExit
376             }
377             osrExit.origin[-1].osrExits << osrExit
378         }
379         @profiledBytecodes = []
380         json["profiledBytecodes"].each {
381             | subJson |
382             @profiledBytecodes << ProfiledBytecodes.new(subJson)
383         }
384         @numInlinedGetByIds = json["numInlinedGetByIds"]
385         @numInlinedPutByIds = json["numInlinedPutByIds"]
386         @numInlinedCalls = json["numInlinedCalls"]
387         @jettisonReason = json["jettisonReason"]
388         @additionalJettisonReason = json["additionalJettisonReason"]
389     end
390     
391     def codeHashSortKey
392         bytecode.codeHashSortKey + "-" + compilationIndex.to_s
393     end
394     
395     def counter(origin)
396         @counters[origin]
397     end
398     
399     def totalCount
400         sum = 0
401         @counters.values.each {
402             | value |
403             sum += value.count
404         }
405         sum
406     end
407     
408     def maxCount
409         max = 0
410         @counters.values.each {
411             | value |
412             max = [max, value.count].max
413         }
414         max
415     end
416     
417     def to_s
418         "#{bytecode}-#{compilationIndex}-#{engine}"
419     end
420 end
421
422 class DescriptionLine
423     attr_reader :actualCountsString, :sourceCountsString, :disassembly, :shouldShow
424     
425     def initialize(actualCountsString, sourceCountsString, disassembly, shouldShow)
426         @actualCountsString = actualCountsString
427         @sourceCountsString = sourceCountsString
428         @disassembly = disassembly
429         @shouldShow = shouldShow
430     end
431     
432     def codeAddress
433         if @disassembly =~ /^\s*(0x[0-9a-fA-F]+):/
434             $1.hex
435         else
436             nil
437         end
438     end
439 end
440
441 def originToPrintStack(origin)
442     (0...(origin.size - 1)).map {
443         | index |
444         "bc\##{origin[index].bytecodeIndex} --> #{origin[index + 1].bytecodes}"
445     }
446 end
447
448 def originToString(origin)
449     (originToPrintStack(origin) + ["bc\##{origin[-1].bytecodeIndex}"]).join(" ")
450 end
451
452 if ARGV.length != 1
453     $stderr.puts "Usage: display-profiler-output <path to profiler output file>"
454     $stderr.puts
455     $stderr.puts "The typical usage pattern for the profiler currently looks something like:"
456     $stderr.puts
457     $stderr.puts "Path/To/jsc -p profile.json myprogram.js"
458     $stderr.puts "display-profiler-output profile.json"
459     exit 1
460 end
461
462 $json = JSON::parse(IO::read(ARGV[0]))
463 $bytecodes = $json["bytecodes"].map {
464     | subJson |
465     Bytecodes.new(subJson)
466 }
467 $compilations = $json["compilations"].map {
468     | subJson |
469     Compilation.new(subJson)
470 }
471 $engines = ["Baseline", "DFG", "FTL", "FTLForOSREntry"]
472
473 def isOptimizing(engine)
474     engine == "DFG" or engine == "FTL" or engine == "FTLForOSREntry"
475 end
476
477 $showCounts = true
478 $sortMode = :time
479
480 def lpad(str,chars)
481   if str.length>chars
482     str
483   else
484     "%#{chars}s"%(str)
485   end
486 end
487
488 def rpad(str, chars)
489     while str.length < chars
490         str += " "
491     end
492     str
493 end
494
495 def center(str, chars)
496     while str.length < chars
497         str += " "
498         if str.length < chars
499             str = " " + str
500         end
501     end
502     str
503 end
504
505 def mayBeHash(hash)
506     hash =~ /#/ or hash.size == 6
507 end
508
509 def sourceOnOneLine(source, limit)
510     source.gsub(/\s+/, ' ')[0...limit]
511 end
512
513 def screenWidth
514     if $stdin.tty?
515         HighLine::SystemExtensions.terminal_size[0]
516     else
517         200
518     end
519 end
520
521 def sortByMode(list)
522     if list.size == 1
523         return list
524     end
525     case $sortMode
526     when :time
527         list
528     when :hash
529         puts "Will sort output by code hash instead of compilation time."
530         puts "Use 'sort time' to change back to the default."
531         puts
532         list.sort { | a, b | a.codeHashSortKey <=> b.codeHashSortKey }
533     else
534         raise
535     end
536 end
537
538 def summary(mode, order)
539     remaining = screenWidth
540     
541     # Figure out how many columns we need for the code block names, and for counts
542     maxCount = 0
543     maxName = 0
544     $bytecodes.each {
545         | bytecodes |
546         maxCount = ([maxCount] + $engines.map {
547                         | engine |
548                         bytecodes.maxTopExecutionCount(engine)
549                     } + $engines.map {
550                         | engine |
551                         bytecodes.maxBottomExecutionCount(engine)
552                     }).max
553         maxName = [bytecodes.to_s.size, maxName].max
554     }
555     maxCountDigits = maxCount.to_s.size
556     
557     hashCols = [[maxName, 30].min, "CodeBlock".size].max
558     remaining -= hashCols + 1
559     
560     countCols = [maxCountDigits * $engines.size + ($engines.size - 1), "Source Counts".size].max
561     remaining -= countCols + 1
562     
563     if mode == :full
564         instructionCountCols = 6
565         remaining -= instructionCountCols + 1
566         
567         machineCountCols = [maxCountDigits * $engines.size, "Machine Counts".size].max
568         remaining -= machineCountCols + 1
569         
570         compilationsCols = 7
571         remaining -= compilationsCols + 1
572         
573         inlinesCols = 9
574         remaining -= inlinesCols + 1
575         
576         exitCountCols = 7
577         remaining -= exitCountCols + 1
578         
579         recentOptsCols = 12
580         remaining -= recentOptsCols + 1
581     end
582     
583     if remaining > 0
584         sourceCols = remaining
585     else
586         sourceCols = nil
587     end
588     
589     print(center("CodeBlock", hashCols))
590     if mode == :full
591         print(" " + center("#Instr", instructionCountCols))
592     end
593     print(" " + center("Source Counts", countCols))
594     if mode == :full
595         print(" " + center("Machine Counts", machineCountCols))
596         print(" " + center("#Compil", compilationsCols))
597         print(" " + center("Inlines", inlinesCols))
598         print(" " + center("#Exits", exitCountCols))
599         print(" " + center("Last Opts", recentOptsCols))
600     end
601     if sourceCols
602         print(" " + center("Source", sourceCols))
603     end
604     puts
605     
606     print(center("", hashCols))
607     if mode == :full
608         print(" " + (" " * instructionCountCols))
609     end
610     print(" " + center("Base/DFG/FTL/FTLOSR", countCols))
611     if mode == :full
612         print(" " + center("Base/DFG/FTL/FTLOSR", machineCountCols))
613         print(" " + (" " * compilationsCols))
614         print(" " + center("Src/Total", inlinesCols))
615         print(" " + (" " * exitCountCols))
616         print(" " + center("Get/Put/Call", recentOptsCols))
617     end
618     puts
619     $bytecodes.sort {
620         | a, b |
621         case order
622         when :bytecode
623             b.totalMaxTopExecutionCount <=> a.totalMaxTopExecutionCount
624         when :machine
625             b.totalMaxBottomExecutionCount <=> a.totalMaxBottomExecutionCount
626         else
627             raise
628         end
629     }.each {
630         | bytecode |
631         print(center(bytecode.name(hashCols), hashCols))
632         if mode == :full
633             print(" " + center(bytecode.instructionCount.to_s, instructionCountCols))
634         end
635         print(" " +
636               center($engines.map {
637                          | engine |
638                          bytecode.maxTopExecutionCount(engine).to_s
639                      }.join("/"), countCols))
640         if mode == :full
641             print(" " + center($engines.map {
642                                    | engine |
643                                    bytecode.maxBottomExecutionCount(engine).to_s
644                                }.join("/"), machineCountCols))
645             print(" " + center(bytecode.compilations.size.to_s, compilationsCols))
646             print(" " + center(bytecode.sourceMachineInlineSites.to_s + "/" + bytecode.totalMachineInlineSites.to_s, inlinesCols))
647             print(" " + center(bytecode.totalExitCount.to_s, exitCountCols))
648             lastCompilation = bytecode.compilations[-1]
649             if lastCompilation
650                 optData = [lastCompilation.numInlinedGetByIds,
651                            lastCompilation.numInlinedPutByIds,
652                            lastCompilation.numInlinedCalls]
653             else
654                 optData = ["N/A"]
655             end
656             print(" " + center(optData.join('/'), recentOptsCols))
657         end
658         if sourceCols
659             print(" " + sourceOnOneLine(bytecode.source, sourceCols))
660         end
661         puts
662     }
663 end
664
665 def queryCompilations(command, args)
666     compilationIndex = nil
667     
668     case args.length
669     when 1
670         hash = args[0]
671         engine = nil
672     when 2
673         if mayBeHash(args[0])
674             hash = args[0]
675             engine = args[1]
676         else
677             engine = args[0]
678             hash = args[1]
679         end
680     else
681         puts "Usage: #{command} <code block hash> <engine>"
682         return
683     end
684     
685     if hash and hash =~ /-(-?[0-9]+)-/
686         hash = $~.pre_match
687         engine = $~.post_match
688         compilationIndex = $1.to_i
689     end
690     
691     if engine and not $engines.index(engine)
692         pattern = Regexp.new(Regexp.escape(engine), "i")
693         trueEngine = nil
694         $engines.each {
695             | myEngine |
696             if myEngine =~ pattern
697                 trueEngine = myEngine
698                 break
699             end
700         }
701         unless trueEngine
702             puts "#{engine} is not a valid engine, try #{$engines.join(' or ')}."
703             return
704         end
705         engine = trueEngine
706     end
707     
708     if hash == "*"
709         hash = nil
710     end
711     
712     sortByMode($compilations).each {
713         | compilation |
714         next if hash and not compilation.bytecode.matches(hash)
715         next if engine and compilation.engine != engine
716         if compilationIndex
717             if compilationIndex < 0
718                 next unless compilation.bytecode.compilations[compilationIndex] == compilation
719             else
720                 next unless compilation.compilationIndex == compilationIndex
721             end
722         end
723         
724         yield compilation
725     }
726 end
727
728 def executeCommand(*commandArray)
729     command = commandArray[0]
730     args = commandArray[1..-1]
731     case command
732     when "help", "h", "?"
733         puts "summary (s)     Print a summary of code block execution rates."
734         puts "full (f)        Same as summary, but prints more information."
735         puts "source          Show the source for a code block."
736         puts "bytecode (b)    Show the bytecode for a code block, with counts."
737         puts "profiling (p)   Show the (internal) profiling data for a code block."
738         puts "log (l)         List the compilations, exits, and jettisons involving this code block."
739         puts "display (d)     Display details for a code block."
740         puts "inlines         Show all inlining stacks that the code block was on."
741         puts "counts          Set whether to show counts for 'bytecode' and 'display'."
742         puts "sort            Set how to sort compilations before display."
743         puts "help (h)        Print this message."
744         puts "quit (q)        Quit."
745     when "quit", "q", "exit"
746         exit 0
747     when "summary", "s"
748         summary(:summary, :bytecode)
749     when "full", "f"
750         if args[0] and (args[0] == "m" or args[0] == "machine")
751             summary(:full, :machine)
752         else
753             summary(:full, :bytecode)
754         end
755     when "source"
756         if args.length != 1
757             puts "Usage: source <code block hash>"
758             return
759         end
760         $bytecodes.each {
761             | bytecode |
762             if bytecode.matches(args[0])
763                 puts bytecode.source
764             end
765         }
766     when "bytecode", "b"
767         if args.length != 1
768             puts "Usage: source <code block hash>"
769             return
770         end
771         
772         hash = args[0]
773         
774         countCols = 10 * $engines.size
775         machineCols = 10 * $engines.size
776         pad = 1
777         while (countCols + 1 + machineCols + pad) % 8 != 0
778             pad += 1
779         end
780         
781         sortByMode($bytecodes).each {
782             | bytecodes |
783             next unless bytecodes.matches(hash)
784             if $showCounts
785                 puts(center("Source Counts", countCols) + " " + center("Machine Counts", machineCols) +
786                      (" " * pad) + center("Bytecode for #{bytecodes}", screenWidth - pad - countCols - 1 - machineCols))
787                 puts(center("Base/DFG/FTL/FTLOSR", countCols) + " " + center("Base/DFG/FTL/FTLOSR", countCols))
788             else
789                 puts("Bytecode for #{bytecodes}:")
790             end
791             bytecodes.each {
792                 | bytecode |
793                 if $showCounts
794                     if bytecode.shouldHaveCounts?
795                         countsString = $engines.map {
796                             | myEngine |
797                             bytecode.topExecutionCount(myEngine)
798                         }.join("/")
799                         machineString = $engines.map {
800                             | myEngine |
801                             bytecode.bottomExecutionCount(myEngine)
802                         }.join("/")
803                     else
804                         countsString = ""
805                         machineString = ""
806                     end
807                     print(center(countsString, countCols) + " " + center(machineString, machineCols) + (" " * pad))
808                 end
809                 puts(bytecode.description.chomp)
810                 bytecode.osrExits.each {
811                     | exit |
812                     if $showCounts
813                         print(center("!!!!!", countCols) + " " + center("!!!!!", machineCols) + (" " * pad))
814                     end
815                     print(" " * 10)
816                     puts("EXIT: in #{exit.compilation} due to #{exit.exitKind}, #{exit.count} times")
817                 }
818             }
819         }
820     when "profiling", "p"
821         if args.length != 1
822             puts "Usage: profiling <code block hash>"
823             return
824         end
825         
826         hash = args[0]
827         
828         first = true
829         sortByMode($compilations).each {
830             | compilation |
831             
832             compilation.profiledBytecodes.each {
833                 | profiledBytecodes |
834                 if profiledBytecodes.bytecodes.matches(hash)
835                     if first
836                         first = false
837                     else
838                         puts
839                     end
840                     
841                     puts "Compilation #{compilation}:"
842                     profiledBytecodes.header.each {
843                         | header |
844                         puts(" " * 6 + header)
845                     }
846                     profiledBytecodes.each {
847                         | bytecode |
848                         puts(" " * 8 + bytecode.description)
849                         profiledBytecodes.bytecodes.bytecode(bytecode.bytecodeIndex).osrExits.each {
850                             | exit |
851                             if exit.compilation == compilation
852                                 puts(" !!!!!           EXIT: due to #{exit.exitKind}, #{exit.count} times")
853                             end
854                         }
855                     }
856                 end
857             }
858         }
859     when "log", "l"
860         queryCompilations("log", args) {
861             | compilation |
862             puts "Compilation #{compilation}:"
863             puts "    Total count: #{compilation.totalCount}  Max count: #{compilation.maxCount}"
864             compilation.osrExits.values.each {
865                 | exits |
866                 exits.each {
867                     | exit |
868                     puts "    EXIT: at #{originToString(exit.origin)} due to #{exit.exitKind}, #{exit.count} times"
869                 }
870             }
871             if compilation.jettisonReason != "NotJettisoned"
872                 puts "    Jettisoned due to #{compilation.jettisonReason}"
873                 if compilation.additionalJettisonReason
874                     puts "        #{compilation.additionalJettisonReason}"
875                 end
876             end
877         }
878     when "inlines"
879         if args.length != 1
880             puts "Usage: inlines <code block hash>"
881             return
882         end
883         
884         hash = args[0]
885         
886         sortByMode($bytecodes).each {
887             | bytecodes |
888             next unless bytecodes.matches(hash)
889             
890             # FIXME: print something useful to say more about which code block this is.
891             
892             $compilations.each {
893                 | compilation |
894                 myOrigins = []
895                 compilation.descriptions.each {
896                     | description |
897                     if description.origin.index {
898                             | myBytecode |
899                             bytecodes == myBytecode.bytecodes
900                         }
901                         myOrigins << description.origin
902                     end
903                 }
904                 myOrigins.uniq!
905                 myOrigins.sort! {
906                     | a, b |
907                     result = 0
908                     [a.size, b.size].min.times {
909                         | index |
910                         result = a[index].bytecodeIndex <=> b[index].bytecodeIndex
911                         break if result != 0
912                     }
913                     result
914                 }
915                 
916                 next if myOrigins.empty?
917
918                 printArray = []
919                 lastPrintStack = []
920                 
921                 def printStack(printArray, stack, lastStack)
922                     stillCommon = true
923                     stack.each_with_index {
924                         | entry, index |
925                         next if stillCommon and entry == lastStack[index]
926                         printArray << ("    " * (index + 1) + entry)
927                         stillCommon = false
928                     }
929                 end
930                 
931                 myOrigins.each {
932                     | origin |
933                     currentPrintStack = originToPrintStack(origin)
934                     printStack(printArray, currentPrintStack, lastPrintStack)
935                     lastPrintStack = currentPrintStack
936                 }
937
938                 next if printArray.empty?
939                 
940                 puts "Compilation #{compilation}:"
941                 printArray.each {
942                     | entry |
943                     puts entry
944                 }
945             }
946         }
947     when "display", "d"
948         actualCountCols = 13
949         sourceCountCols = 10 * $engines.size
950         
951         first = true
952
953         queryCompilations("display", args) {
954             | compilation |
955             
956             if first
957                 first = false
958             else
959                 puts
960             end
961             
962             puts("Compilation #{compilation}:")
963             if $showCounts
964                 puts(center("Actual Counts", actualCountCols) + " " + center("Source Counts", sourceCountCols) + " " + center("Disassembly in #{compilation.engine}", screenWidth - 1 - sourceCountCols - 1 - actualCountCols))
965                 puts((" " * actualCountCols) + " " + center("Base/DFG/FTL/FTLOSR", sourceCountCols))
966             else
967                 puts("Disassembly in #{compilation.engine}")
968             end
969                 
970             lines = []
971
972             compilation.descriptions.each {
973                 | description |
974                 # FIXME: We should have a better way of detecting things like CountExecution nodes
975                 # and slow path entries in the baseline JIT.
976                 if description.description =~ /CountExecution\(/ and isOptimizing(compilation.engine)
977                     shouldShow = false
978                 else
979                     shouldShow = true
980                 end
981                 if not description.origin.empty? and not description.origin[-1]
982                     p description.origin
983                     p description.description
984                 end
985                 if description.origin.empty? or not description.origin[-1].shouldHaveCounts? or (compilation.engine == "Baseline" and description.description =~ /^\s*\(S\)/)
986                     actualCountsString = ""
987                     sourceCountsString = ""
988                 else
989                     actualCountsString = compilation.counter(description.origin).count.to_s
990                     sourceCountsString = $engines.map {
991                         | myEngine |
992                         description.origin[-1].topExecutionCount(myEngine)
993                     }.join("/")
994                 end
995                 description.description.split("\n").each {
996                     | line |
997                     lines << DescriptionLine.new(actualCountsString, sourceCountsString, line.chomp, shouldShow)
998                 }
999             }
1000             
1001             exitPrefix = ""
1002             if $showCounts
1003                 exitPrefix += center("!!!!!", actualCountCols) + " " + center("!!!!!", sourceCountCols) + (" " * 15)
1004             else
1005                 exitPrefix += "   !!!!!"
1006             end
1007             exitPrefix += " " * 10
1008
1009             lines.each_with_index {
1010                 | line, index |
1011                 codeAddress = line.codeAddress
1012                 if codeAddress
1013                     list = compilation.osrExits[codeAddress]
1014                     if list
1015                         list.each {
1016                             | exit |
1017                             if exit.isWatchpoint
1018                                 exit.dumpForDisplay(exitPrefix)
1019                             end
1020                         }
1021                     end
1022                 end
1023                 if line.shouldShow
1024                     if $showCounts
1025                         print(center(line.actualCountsString, actualCountCols) + " " + center(line.sourceCountsString, sourceCountCols) + " ")
1026                     end
1027                     puts(line.disassembly)
1028                 end
1029                 if codeAddress
1030                     # Find the next disassembly address.
1031                     endIndex = index + 1
1032                     endAddress = nil
1033                     while endIndex < lines.size
1034                         myAddress = lines[endIndex].codeAddress
1035                         if myAddress
1036                             endAddress = myAddress
1037                             break
1038                         end
1039                         endIndex += 1
1040                     end
1041                     
1042                     if endAddress
1043                         list = compilation.osrExits[endAddress]
1044                         if list
1045                             list.each {
1046                                 | exit |
1047                                 unless exit.isWatchpoint
1048                                     exit.dumpForDisplay(exitPrefix)
1049                                 end
1050                             }
1051                         end
1052                     end
1053                 end
1054             }
1055         }
1056     when "counts"
1057         if args.length != 1
1058             puts "Usage: counts on|off|toggle"
1059         else
1060             case args[0].downcase
1061             when 'on'
1062                 $showCounts = true
1063             when 'off'
1064                 $showCounts = false
1065             when 'toggle'
1066                 $showCounts = !$showCounts
1067             else
1068                 puts "Usage: counts on|off|toggle"
1069             end
1070         end
1071         puts "Current value: #{$showCounts ? 'on' : 'off'}"
1072     when "sort"
1073         if args.length != 1
1074             puts "Usage: sort time|hash"
1075             puts
1076             puts "sort time: Sorts by the timestamp of when the code was compiled."
1077             puts "           This is the default."
1078             puts
1079             puts "sort hash: Sorts by the code hash. This is more deterministic,"
1080             puts "           and is useful for diffs."
1081             puts
1082         else
1083             case args[0].downcase
1084             when 'time'
1085                 $sortMode = :time
1086             when 'hash'
1087                 $sortMode = :hash
1088             else
1089                 puts "Usage: sort time|hash"
1090             end
1091         end
1092         puts "Current value: #{$sortMode}"
1093     else
1094         puts "Invalid command: #{command}"
1095     end
1096 end
1097
1098 if $stdin.tty?
1099     executeCommand("full")
1100 end
1101
1102 while commandLine = Readline.readline("> ", true)
1103     executeCommand(*commandLine.split)
1104 end
1105