pytest is not correctly auto-installed
[WebKit-https.git] / Tools / Scripts / display-profiler-output
1 #!/usr/bin/env ruby
2
3 # Copyright (C) 2012-2014, 2016 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, :uid
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         @uid = json["uid"]
390     end
391     
392     def codeHashSortKey
393         bytecode.codeHashSortKey + "-" + compilationIndex.to_s
394     end
395     
396     def counter(origin)
397         @counters[origin]
398     end
399     
400     def totalCount
401         sum = 0
402         @counters.values.each {
403             | value |
404             sum += value.count
405         }
406         sum
407     end
408     
409     def maxCount
410         max = 0
411         @counters.values.each {
412             | value |
413             max = [max, value.count].max
414         }
415         max
416     end
417     
418     def to_s
419         "#{bytecode}-#{compilationIndex}-#{engine}"
420     end
421 end
422
423 class DescriptionLine
424     attr_reader :actualCountsString, :sourceCountsString, :disassembly, :shouldShow
425     
426     def initialize(actualCountsString, sourceCountsString, disassembly, shouldShow)
427         @actualCountsString = actualCountsString
428         @sourceCountsString = sourceCountsString
429         @disassembly = disassembly
430         @shouldShow = shouldShow
431     end
432     
433     def codeAddress
434         if @disassembly =~ /^\s*(0x[0-9a-fA-F]+):/
435             $1.hex
436         else
437             nil
438         end
439     end
440 end
441
442 class Event
443     attr_reader :time, :bytecode, :compilation, :summary, :detail
444     
445     def initialize(json)
446         @time = json["time"].to_f
447         @bytecode = $bytecodes[json["bytecodesID"].to_i]
448         if json["compilationUID"]
449             @compilation = $compilationMap[json["compilationUID"]]
450         end
451         @summary = json["summary"]
452         @detail = json["detail"]
453     end
454 end
455
456 def originToPrintStack(origin)
457     (0...(origin.size - 1)).map {
458         | index |
459         "bc\##{origin[index].bytecodeIndex} --> #{origin[index + 1].bytecodes}"
460     }
461 end
462
463 def originToString(origin)
464     (originToPrintStack(origin) + ["bc\##{origin[-1].bytecodeIndex}"]).join(" ")
465 end
466
467 if ARGV.length != 1
468     $stderr.puts "Usage: display-profiler-output <path to profiler output file>"
469     $stderr.puts
470     $stderr.puts "The typical usage pattern for the profiler currently looks something like:"
471     $stderr.puts
472     $stderr.puts "Path/To/jsc -p profile.json myprogram.js"
473     $stderr.puts "display-profiler-output profile.json"
474     exit 1
475 end
476
477 $json = JSON::parse(IO::read(ARGV[0]))
478 $bytecodes = $json["bytecodes"].map {
479     | subJson |
480     Bytecodes.new(subJson)
481 }
482 $compilations = $json["compilations"].map {
483     | subJson |
484     Compilation.new(subJson)
485 }
486 $compilationMap = {}
487 $compilations.each {
488     | compilation |
489     $compilationMap[compilation.uid] = compilation
490 }
491 $events = $json["events"].map {
492     | subJson |
493     Event.new(subJson)
494 }
495 $engines = ["Baseline", "DFG", "FTL", "FTLForOSREntry"]
496
497 def isOptimizing(engine)
498     engine == "DFG" or engine == "FTL" or engine == "FTLForOSREntry"
499 end
500
501 $showCounts = true
502 $sortMode = :time
503
504 def lpad(str, chars)
505     str = str.to_s
506     if str.length > chars
507         str
508     else
509         "%#{chars}s"%(str)
510     end
511 end
512
513 def rpad(str, chars)
514     str = str.to_s
515     while str.length < chars
516         str += " "
517     end
518     str
519 end
520
521 def center(str, chars)
522     str = str.to_s
523     while str.length < chars
524         str += " "
525         if str.length < chars
526             str = " " + str
527         end
528     end
529     str
530 end
531
532 def mayBeHash(hash)
533     hash =~ /#/ or hash.size == 6
534 end
535
536 def sourceOnOneLine(source, limit)
537     source.gsub(/\s+/, ' ')[0...limit]
538 end
539
540 def screenWidth
541     if $stdin.tty?
542         HighLine::SystemExtensions.terminal_size[0] - 3
543     else
544         200
545     end
546 end
547
548 def sortByMode(list)
549     if list.size == 1
550         return list
551     end
552     case $sortMode
553     when :time
554         list
555     when :hash
556         puts "Will sort output by code hash instead of compilation time."
557         puts "Use 'sort time' to change back to the default."
558         puts
559         list.sort { | a, b | a.codeHashSortKey <=> b.codeHashSortKey }
560     else
561         raise
562     end
563 end
564
565 def summary(mode, order)
566     remaining = screenWidth
567     
568     # Figure out how many columns we need for the code block names, and for counts
569     maxCount = 0
570     maxName = 0
571     $bytecodes.each {
572         | bytecodes |
573         maxCount = ([maxCount] + $engines.map {
574                         | engine |
575                         bytecodes.maxTopExecutionCount(engine)
576                     } + $engines.map {
577                         | engine |
578                         bytecodes.maxBottomExecutionCount(engine)
579                     }).max
580         maxName = [bytecodes.to_s.size, maxName].max
581     }
582     maxCountDigits = maxCount.to_s.size
583     
584     hashCols = [[maxName, 30].min, "CodeBlock".size].max
585     remaining -= hashCols + 1
586     
587     countCols = [maxCountDigits * $engines.size + ($engines.size - 1), "Source Counts".size].max
588     remaining -= countCols + 1
589     
590     if mode == :full
591         instructionCountCols = 6
592         remaining -= instructionCountCols + 1
593         
594         machineCountCols = [maxCountDigits * $engines.size, "Machine Counts".size].max
595         remaining -= machineCountCols + 1
596         
597         compilationsCols = 7
598         remaining -= compilationsCols + 1
599         
600         inlinesCols = 9
601         remaining -= inlinesCols + 1
602         
603         exitCountCols = 7
604         remaining -= exitCountCols + 1
605         
606         recentOptsCols = 12
607         remaining -= recentOptsCols + 1
608     end
609     
610     if remaining > 0
611         sourceCols = remaining
612     else
613         sourceCols = nil
614     end
615     
616     print(center("CodeBlock", hashCols))
617     if mode == :full
618         print(" " + center("#Instr", instructionCountCols))
619     end
620     print(" " + center("Source Counts", countCols))
621     if mode == :full
622         print(" " + center("Machine Counts", machineCountCols))
623         print(" " + center("#Compil", compilationsCols))
624         print(" " + center("Inlines", inlinesCols))
625         print(" " + center("#Exits", exitCountCols))
626         print(" " + center("Last Opts", recentOptsCols))
627     end
628     if sourceCols
629         print(" " + center("Source", sourceCols))
630     end
631     puts
632     
633     print(center("", hashCols))
634     if mode == :full
635         print(" " + (" " * instructionCountCols))
636     end
637     print(" " + center("Base/DFG/FTL/FTLOSR", countCols))
638     if mode == :full
639         print(" " + center("Base/DFG/FTL/FTLOSR", machineCountCols))
640         print(" " + (" " * compilationsCols))
641         print(" " + center("Src/Total", inlinesCols))
642         print(" " + (" " * exitCountCols))
643         print(" " + center("Get/Put/Call", recentOptsCols))
644     end
645     puts
646     $bytecodes.sort {
647         | a, b |
648         case order
649         when :bytecode
650             b.totalMaxTopExecutionCount <=> a.totalMaxTopExecutionCount
651         when :machine
652             b.totalMaxBottomExecutionCount <=> a.totalMaxBottomExecutionCount
653         when :exits
654             b.totalExitCount <=> a.totalExitCount
655         when :compiles
656             b.compilations.size <=> a.compilations.size
657         else
658             raise
659         end
660     }.each {
661         | bytecode |
662         print(center(bytecode.name(hashCols), hashCols))
663         if mode == :full
664             print(" " + center(bytecode.instructionCount.to_s, instructionCountCols))
665         end
666         print(" " +
667               center($engines.map {
668                          | engine |
669                          bytecode.maxTopExecutionCount(engine).to_s
670                      }.join("/"), countCols))
671         if mode == :full
672             print(" " + center($engines.map {
673                                    | engine |
674                                    bytecode.maxBottomExecutionCount(engine).to_s
675                                }.join("/"), machineCountCols))
676             print(" " + center(bytecode.compilations.size.to_s, compilationsCols))
677             print(" " + center(bytecode.sourceMachineInlineSites.to_s + "/" + bytecode.totalMachineInlineSites.to_s, inlinesCols))
678             print(" " + center(bytecode.totalExitCount.to_s, exitCountCols))
679             lastCompilation = bytecode.compilations[-1]
680             if lastCompilation
681                 optData = [lastCompilation.numInlinedGetByIds,
682                            lastCompilation.numInlinedPutByIds,
683                            lastCompilation.numInlinedCalls]
684             else
685                 optData = ["N/A"]
686             end
687             print(" " + center(optData.join('/'), recentOptsCols))
688         end
689         if sourceCols
690             print(" " + sourceOnOneLine(bytecode.source, sourceCols))
691         end
692         puts
693     }
694 end
695
696 def queryCompilations(command, args)
697     compilationIndex = nil
698     
699     case args.length
700     when 1
701         hash = args[0]
702         engine = nil
703     when 2
704         if mayBeHash(args[0])
705             hash = args[0]
706             engine = args[1]
707         else
708             engine = args[0]
709             hash = args[1]
710         end
711     else
712         puts "Usage: #{command} <code block hash> <engine>"
713         return
714     end
715     
716     if hash and hash =~ /-(-?[0-9]+)-/
717         hash = $~.pre_match
718         engine = $~.post_match
719         compilationIndex = $1.to_i
720     end
721     
722     if engine and not $engines.index(engine)
723         pattern = Regexp.new(Regexp.escape(engine), "i")
724         trueEngine = nil
725         $engines.each {
726             | myEngine |
727             if myEngine =~ pattern
728                 trueEngine = myEngine
729                 break
730             end
731         }
732         unless trueEngine
733             puts "#{engine} is not a valid engine, try #{$engines.join(' or ')}."
734             return
735         end
736         engine = trueEngine
737     end
738     
739     if hash == "*"
740         hash = nil
741     end
742     
743     sortByMode($compilations).each {
744         | compilation |
745         next if hash and not compilation.bytecode.matches(hash)
746         next if engine and compilation.engine != engine
747         if compilationIndex
748             if compilationIndex < 0
749                 next unless compilation.bytecode.compilations[compilationIndex] == compilation
750             else
751                 next unless compilation.compilationIndex == compilationIndex
752             end
753         end
754         
755         yield compilation
756     }
757 end
758
759 def executeCommand(*commandArray)
760     command = commandArray[0]
761     args = commandArray[1..-1]
762     case command
763     when "help", "h", "?"
764         puts "summary (s)     Print a summary of code block execution rates."
765         puts "full (f)        Same as summary, but prints more information."
766         puts "source          Show the source for a code block."
767         puts "bytecode (b)    Show the bytecode for a code block, with counts."
768         puts "profiling (p)   Show the (internal) profiling data for a code block."
769         puts "log (l)         List the compilations, exits, and jettisons involving this code block."
770         puts "events (e)      List of events involving this code block."
771         puts "display (d)     Display details for a code block."
772         puts "inlines         Show all inlining stacks that the code block was on."
773         puts "counts          Set whether to show counts for 'bytecode' and 'display'."
774         puts "sort            Set how to sort compilations before display."
775         puts "help (h)        Print this message."
776         puts "quit (q)        Quit."
777     when "quit", "q", "exit"
778         exit 0
779     when "summary", "s"
780         summary(:summary, :bytecode)
781     when "full", "f"
782         if args[0] and (args[0] == "m" or args[0] == "machine")
783             summary(:full, :machine)
784         elsif args[0] and (args[0] == "e" or args[0] == "exits")
785             summary(:full, :exits)
786         elsif args[0] and (args[0] == "c" or args[0] == "compiles")
787             summary(:full, :compiles)
788         else
789             summary(:full, :bytecode)
790         end
791     when "source"
792         if args.length != 1
793             puts "Usage: source <code block hash>"
794             return
795         end
796         $bytecodes.each {
797             | bytecode |
798             if bytecode.matches(args[0])
799                 puts bytecode.source
800             end
801         }
802     when "bytecode", "b"
803         if args.length != 1
804             puts "Usage: source <code block hash>"
805             return
806         end
807         
808         hash = args[0]
809         
810         countCols = 10 * $engines.size
811         machineCols = 10 * $engines.size
812         pad = 1
813         while (countCols + 1 + machineCols + pad) % 8 != 0
814             pad += 1
815         end
816         
817         sortByMode($bytecodes).each {
818             | bytecodes |
819             next unless bytecodes.matches(hash)
820             if $showCounts
821                 puts(center("Source Counts", countCols) + " " + center("Machine Counts", machineCols) +
822                      (" " * pad) + center("Bytecode for #{bytecodes}", screenWidth - pad - countCols - 1 - machineCols))
823                 puts(center("Base/DFG/FTL/FTLOSR", countCols) + " " + center("Base/DFG/FTL/FTLOSR", countCols))
824             else
825                 puts("Bytecode for #{bytecodes}:")
826             end
827             bytecodes.each {
828                 | bytecode |
829                 if $showCounts
830                     if bytecode.shouldHaveCounts?
831                         countsString = $engines.map {
832                             | myEngine |
833                             bytecode.topExecutionCount(myEngine)
834                         }.join("/")
835                         machineString = $engines.map {
836                             | myEngine |
837                             bytecode.bottomExecutionCount(myEngine)
838                         }.join("/")
839                     else
840                         countsString = ""
841                         machineString = ""
842                     end
843                     print(center(countsString, countCols) + " " + center(machineString, machineCols) + (" " * pad))
844                 end
845                 puts(bytecode.description.chomp)
846                 bytecode.osrExits.each {
847                     | exit |
848                     if $showCounts
849                         print(center("!!!!!", countCols) + " " + center("!!!!!", machineCols) + (" " * pad))
850                     end
851                     print(" " * 10)
852                     puts("EXIT: in #{exit.compilation} due to #{exit.exitKind}, #{exit.count} times")
853                 }
854             }
855         }
856     when "profiling", "p"
857         if args.length != 1
858             puts "Usage: profiling <code block hash>"
859             return
860         end
861         
862         hash = args[0]
863         
864         first = true
865         sortByMode($compilations).each {
866             | compilation |
867             
868             compilation.profiledBytecodes.each {
869                 | profiledBytecodes |
870                 if profiledBytecodes.bytecodes.matches(hash)
871                     if first
872                         first = false
873                     else
874                         puts
875                     end
876                     
877                     puts "Compilation #{compilation}:"
878                     profiledBytecodes.header.each {
879                         | header |
880                         puts(" " * 6 + header)
881                     }
882                     profiledBytecodes.each {
883                         | bytecode |
884                         puts(" " * 8 + bytecode.description)
885                         profiledBytecodes.bytecodes.bytecode(bytecode.bytecodeIndex).osrExits.each {
886                             | exit |
887                             if exit.compilation == compilation
888                                 puts(" !!!!!           EXIT: due to #{exit.exitKind}, #{exit.count} times")
889                             end
890                         }
891                     }
892                 end
893             }
894         }
895     when "log", "l"
896         queryCompilations("log", args) {
897             | compilation |
898             puts "Compilation #{compilation}:"
899             puts "    Total count: #{compilation.totalCount}  Max count: #{compilation.maxCount}"
900             compilation.osrExits.values.each {
901                 | exits |
902                 exits.each {
903                     | exit |
904                     puts "    EXIT: at #{originToString(exit.origin)} due to #{exit.exitKind}, #{exit.count} times"
905                 }
906             }
907             if compilation.jettisonReason != "NotJettisoned"
908                 puts "    Jettisoned due to #{compilation.jettisonReason}"
909                 if compilation.additionalJettisonReason
910                     puts "        #{compilation.additionalJettisonReason}"
911                 end
912             end
913         }
914     when "events", "e"
915         if args.length != 1
916             puts "Usage: inlines <code block hash>"
917             return
918         end
919         
920         hash = Regexp.new(Regexp.escape(args[0]))
921         
922         events = []
923         $events.each {
924             | event |
925             if event.bytecode.to_s =~ hash
926                 events << event
927             end
928         }
929         
930         timeCols = 0
931         hashCols = 0
932         compilationCols = 0
933         summaryCols = 0
934         events.each {
935             | event |
936             timeCols = [event.time.to_s.size, timeCols].max
937             hashCols = [event.bytecode.to_s.size, hashCols].max
938             if event.compilation
939                 compilationCols = [event.compilation.to_s.size, compilationCols].max
940             end
941             summaryCols = [event.summary.size, summaryCols].max
942         }
943         
944         events.each {
945             | event |
946             print rpad(event.time.to_s, timeCols)
947             print " "
948             print rpad(event.bytecode.to_s, hashCols)
949             print " "
950             compilationStr = ""
951             if event.compilation
952                 compilationStr = event.compilation.to_s
953             end
954             print rpad(compilationStr, compilationCols)
955             print " "
956             print rpad(event.summary, summaryCols)
957             print " "
958             puts event.detail
959         }
960     when "inlines"
961         if args.length != 1
962             puts "Usage: inlines <code block hash>"
963             return
964         end
965         
966         hash = args[0]
967         
968         sortByMode($bytecodes).each {
969             | bytecodes |
970             next unless bytecodes.matches(hash)
971             
972             # FIXME: print something useful to say more about which code block this is.
973             
974             $compilations.each {
975                 | compilation |
976                 myOrigins = []
977                 compilation.descriptions.each {
978                     | description |
979                     if description.origin.index {
980                             | myBytecode |
981                             bytecodes == myBytecode.bytecodes
982                         }
983                         myOrigins << description.origin
984                     end
985                 }
986                 myOrigins.uniq!
987                 myOrigins.sort! {
988                     | a, b |
989                     result = 0
990                     [a.size, b.size].min.times {
991                         | index |
992                         result = a[index].bytecodeIndex <=> b[index].bytecodeIndex
993                         break if result != 0
994                     }
995                     result
996                 }
997                 
998                 next if myOrigins.empty?
999
1000                 printArray = []
1001                 lastPrintStack = []
1002                 
1003                 def printStack(printArray, stack, lastStack)
1004                     stillCommon = true
1005                     stack.each_with_index {
1006                         | entry, index |
1007                         next if stillCommon and entry == lastStack[index]
1008                         printArray << ("    " * (index + 1) + entry)
1009                         stillCommon = false
1010                     }
1011                 end
1012                 
1013                 myOrigins.each {
1014                     | origin |
1015                     currentPrintStack = originToPrintStack(origin)
1016                     printStack(printArray, currentPrintStack, lastPrintStack)
1017                     lastPrintStack = currentPrintStack
1018                 }
1019
1020                 next if printArray.empty?
1021                 
1022                 puts "Compilation #{compilation}:"
1023                 printArray.each {
1024                     | entry |
1025                     puts entry
1026                 }
1027             }
1028         }
1029     when "display", "d"
1030         actualCountCols = 13
1031         sourceCountCols = 10 * $engines.size
1032         
1033         first = true
1034
1035         queryCompilations("display", args) {
1036             | compilation |
1037             
1038             if first
1039                 first = false
1040             else
1041                 puts
1042             end
1043             
1044             puts("Compilation #{compilation}:")
1045             if $showCounts
1046                 puts(center("Actual Counts", actualCountCols) + " " + center("Source Counts", sourceCountCols) + " " + center("Disassembly in #{compilation.engine}", screenWidth - 1 - sourceCountCols - 1 - actualCountCols))
1047                 puts((" " * actualCountCols) + " " + center("Base/DFG/FTL/FTLOSR", sourceCountCols))
1048             else
1049                 puts("Disassembly in #{compilation.engine}")
1050             end
1051                 
1052             lines = []
1053
1054             compilation.descriptions.each {
1055                 | description |
1056                 # FIXME: We should have a better way of detecting things like CountExecution nodes
1057                 # and slow path entries in the baseline JIT.
1058                 if description.description =~ /CountExecution\(/ and isOptimizing(compilation.engine)
1059                     shouldShow = false
1060                 else
1061                     shouldShow = true
1062                 end
1063                 if not description.origin.empty? and not description.origin[-1]
1064                     p description.origin
1065                     p description.description
1066                 end
1067                 if description.origin.empty? or not description.origin[-1].shouldHaveCounts? or (compilation.engine == "Baseline" and description.description =~ /^\s*\(S\)/)
1068                     actualCountsString = ""
1069                     sourceCountsString = ""
1070                 else
1071                     actualCountsString = compilation.counter(description.origin).count.to_s
1072                     sourceCountsString = $engines.map {
1073                         | myEngine |
1074                         description.origin[-1].topExecutionCount(myEngine)
1075                     }.join("/")
1076                 end
1077                 description.description.split("\n").each {
1078                     | line |
1079                     lines << DescriptionLine.new(actualCountsString, sourceCountsString, line.chomp, shouldShow)
1080                 }
1081             }
1082             
1083             exitPrefix = ""
1084             if $showCounts
1085                 exitPrefix += center("!!!!!", actualCountCols) + " " + center("!!!!!", sourceCountCols) + (" " * 15)
1086             else
1087                 exitPrefix += "   !!!!!"
1088             end
1089             exitPrefix += " " * 10
1090
1091             lines.each_with_index {
1092                 | line, index |
1093                 codeAddress = line.codeAddress
1094                 if codeAddress
1095                     list = compilation.osrExits[codeAddress]
1096                     if list
1097                         list.each {
1098                             | exit |
1099                             if exit.isWatchpoint
1100                                 exit.dumpForDisplay(exitPrefix)
1101                             end
1102                         }
1103                     end
1104                 end
1105                 if line.shouldShow
1106                     if $showCounts
1107                         print(center(line.actualCountsString, actualCountCols) + " " + center(line.sourceCountsString, sourceCountCols) + " ")
1108                     end
1109                     puts(line.disassembly)
1110                 end
1111                 if codeAddress
1112                     # Find the next disassembly address.
1113                     endIndex = index + 1
1114                     endAddress = nil
1115                     while endIndex < lines.size
1116                         myAddress = lines[endIndex].codeAddress
1117                         if myAddress
1118                             endAddress = myAddress
1119                             break
1120                         end
1121                         endIndex += 1
1122                     end
1123                     
1124                     if endAddress
1125                         list = compilation.osrExits[endAddress]
1126                         if list
1127                             list.each {
1128                                 | exit |
1129                                 unless exit.isWatchpoint
1130                                     exit.dumpForDisplay(exitPrefix)
1131                                 end
1132                             }
1133                         end
1134                     end
1135                 end
1136             }
1137         }
1138     when "counts"
1139         if args.length != 1
1140             puts "Usage: counts on|off|toggle"
1141         else
1142             case args[0].downcase
1143             when 'on'
1144                 $showCounts = true
1145             when 'off'
1146                 $showCounts = false
1147             when 'toggle'
1148                 $showCounts = !$showCounts
1149             else
1150                 puts "Usage: counts on|off|toggle"
1151             end
1152         end
1153         puts "Current value: #{$showCounts ? 'on' : 'off'}"
1154     when "sort"
1155         if args.length != 1
1156             puts "Usage: sort time|hash"
1157             puts
1158             puts "sort time: Sorts by the timestamp of when the code was compiled."
1159             puts "           This is the default."
1160             puts
1161             puts "sort hash: Sorts by the code hash. This is more deterministic,"
1162             puts "           and is useful for diffs."
1163             puts
1164         else
1165             case args[0].downcase
1166             when 'time'
1167                 $sortMode = :time
1168             when 'hash'
1169                 $sortMode = :hash
1170             else
1171                 puts "Usage: sort time|hash"
1172             end
1173         end
1174         puts "Current value: #{$sortMode}"
1175     else
1176         puts "Invalid command: #{command}"
1177     end
1178 end
1179
1180 if $stdin.tty?
1181     executeCommand("full")
1182 end
1183
1184 while commandLine = Readline.readline("> ", true)
1185     executeCommand(*commandLine.split)
1186 end
1187