Initialize Tizen 2.3
[framework/web/webkit-efl.git] / Tools / Scripts / bencher
1 #!/usr/bin/env ruby
2
3 # Copyright (C) 2011 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 'getoptlong'
29 require 'pathname'
30 require 'tempfile'
31 require 'socket'
32
33 begin
34   require 'json'
35 rescue LoadError => e
36   $stderr.puts "It does not appear that you have the 'json' package installed.  Try running 'sudo gem install json'."
37   exit 1
38 end
39
40 # Configuration
41
42 CONFIGURATION_FLNM = ENV["HOME"]+"/.bencher"
43
44 unless FileTest.exist? CONFIGURATION_FLNM
45   $stderr.puts "Error: no configuration file at ~/.bencher."
46   $stderr.puts "This file should contain paths to SunSpider, V8, and Kraken, as well as a"
47   $stderr.puts "temporary directory that bencher can use for its remote mode. It should be"
48   $stderr.puts "formatted in JSON.  For example:"
49   $stderr.puts "{"
50   $stderr.puts "    \"sunSpiderPath\": \"/Volumes/Data/pizlo/OpenSource/PerformanceTests/SunSpider/tests/sunspider-1.0\","
51   $stderr.puts "    \"v8Path\": \"/Volumes/Data/pizlo/OpenSource/PerformanceTests/SunSpider/tests/v8-v6\","
52   $stderr.puts "    \"krakenPath\": \"/Volumes/Data/pizlo/kraken/kraken-e119421cb325/tests/kraken-1.1\","
53   $stderr.puts "    \"tempPath\": \"/Volumes/Data/pizlo/bencher/temp\""
54   $stderr.puts "}"
55   exit 1
56 end
57
58 CONFIGURATION = JSON.parse(File::read(CONFIGURATION_FLNM))
59
60 SUNSPIDER_PATH = CONFIGURATION["sunSpiderPath"]
61 V8_PATH = CONFIGURATION["v8Path"]
62 KRAKEN_PATH = CONFIGURATION["krakenPath"]
63 TEMP_PATH = CONFIGURATION["tempPath"]
64 BENCH_DATA_PATH = TEMP_PATH + "/benchdata"
65
66 IBR_LOOKUP=[0.00615583, 0.0975, 0.22852, 0.341628, 0.430741, 0.500526, 0.555933, 
67             0.600706, 0.637513, 0.668244, 0.694254, 0.716537, 0.735827, 0.752684, 
68             0.767535, 0.780716, 0.792492, 0.803074, 0.812634, 0.821313, 0.829227, 
69             0.836472, 0.843129, 0.849267, 0.854943, 0.860209, 0.865107, 0.869674, 
70             0.873942, 0.877941, 0.881693, 0.885223, 0.888548, 0.891686, 0.894652, 
71             0.897461, 0.900124, 0.902652, 0.905056, 0.907343, 0.909524, 0.911604, 
72             0.91359, 0.91549, 0.917308, 0.919049, 0.920718, 0.92232, 0.923859, 0.925338, 
73             0.926761, 0.92813, 0.929449, 0.930721, 0.931948, 0.933132, 0.934275, 0.93538, 
74             0.936449, 0.937483, 0.938483, 0.939452, 0.940392, 0.941302, 0.942185, 
75             0.943042, 0.943874, 0.944682, 0.945467, 0.94623, 0.946972, 0.947694, 
76             0.948396, 0.94908, 0.949746, 0.950395, 0.951027, 0.951643, 0.952244, 
77             0.952831, 0.953403, 0.953961, 0.954506, 0.955039, 0.955559, 0.956067, 
78             0.956563, 0.957049, 0.957524, 0.957988, 0.958443, 0.958887, 0.959323, 
79             0.959749, 0.960166, 0.960575, 0.960975, 0.961368, 0.961752, 0.962129, 
80             0.962499, 0.962861, 0.963217, 0.963566, 0.963908, 0.964244, 0.964574, 
81             0.964897, 0.965215, 0.965527, 0.965834, 0.966135, 0.966431, 0.966722, 
82             0.967007, 0.967288, 0.967564, 0.967836, 0.968103, 0.968366, 0.968624, 
83             0.968878, 0.969128, 0.969374, 0.969617, 0.969855, 0.97009, 0.970321, 
84             0.970548, 0.970772, 0.970993, 0.97121, 0.971425, 0.971636, 0.971843, 
85             0.972048, 0.97225, 0.972449, 0.972645, 0.972839, 0.973029, 0.973217, 
86             0.973403, 0.973586, 0.973766, 0.973944, 0.97412, 0.974293, 0.974464, 
87             0.974632, 0.974799, 0.974963, 0.975125, 0.975285, 0.975443, 0.975599, 
88             0.975753, 0.975905, 0.976055, 0.976204, 0.97635, 0.976495, 0.976638, 
89             0.976779, 0.976918, 0.977056, 0.977193, 0.977327, 0.97746, 0.977592, 
90             0.977722, 0.97785, 0.977977, 0.978103, 0.978227, 0.978349, 0.978471, 
91             0.978591, 0.978709, 0.978827, 0.978943, 0.979058, 0.979171, 0.979283, 
92             0.979395, 0.979504, 0.979613, 0.979721, 0.979827, 0.979933, 0.980037, 
93             0.98014, 0.980242, 0.980343, 0.980443, 0.980543, 0.980641, 0.980738, 
94             0.980834, 0.980929, 0.981023, 0.981116, 0.981209, 0.9813, 0.981391, 0.981481, 
95             0.981569, 0.981657, 0.981745, 0.981831, 0.981916, 0.982001, 0.982085, 
96             0.982168, 0.982251, 0.982332, 0.982413, 0.982493, 0.982573, 0.982651, 
97             0.982729, 0.982807, 0.982883, 0.982959, 0.983034, 0.983109, 0.983183, 
98             0.983256, 0.983329, 0.983401, 0.983472, 0.983543, 0.983613, 0.983683, 
99             0.983752, 0.98382, 0.983888, 0.983956, 0.984022, 0.984089, 0.984154, 
100             0.984219, 0.984284, 0.984348, 0.984411, 0.984474, 0.984537, 0.984599, 
101             0.98466, 0.984721, 0.984782, 0.984842, 0.984902, 0.984961, 0.985019, 
102             0.985077, 0.985135, 0.985193, 0.985249, 0.985306, 0.985362, 0.985417, 
103             0.985472, 0.985527, 0.985582, 0.985635, 0.985689, 0.985742, 0.985795, 
104             0.985847, 0.985899, 0.985951, 0.986002, 0.986053, 0.986103, 0.986153, 
105             0.986203, 0.986252, 0.986301, 0.98635, 0.986398, 0.986446, 0.986494, 
106             0.986541, 0.986588, 0.986635, 0.986681, 0.986727, 0.986773, 0.986818, 
107             0.986863, 0.986908, 0.986953, 0.986997, 0.987041, 0.987084, 0.987128, 
108             0.987171, 0.987213, 0.987256, 0.987298, 0.98734, 0.987381, 0.987423, 
109             0.987464, 0.987504, 0.987545, 0.987585, 0.987625, 0.987665, 0.987704, 
110             0.987744, 0.987783, 0.987821, 0.98786, 0.987898, 0.987936, 0.987974, 
111             0.988011, 0.988049, 0.988086, 0.988123, 0.988159, 0.988196, 0.988232, 
112             0.988268, 0.988303, 0.988339, 0.988374, 0.988409, 0.988444, 0.988479, 
113             0.988513, 0.988547, 0.988582, 0.988615, 0.988649, 0.988682, 0.988716, 
114             0.988749, 0.988782, 0.988814, 0.988847, 0.988879, 0.988911, 0.988943, 
115             0.988975, 0.989006, 0.989038, 0.989069, 0.9891, 0.989131, 0.989161, 0.989192, 
116             0.989222, 0.989252, 0.989282, 0.989312, 0.989342, 0.989371, 0.989401, 
117             0.98943, 0.989459, 0.989488, 0.989516, 0.989545, 0.989573, 0.989602, 0.98963, 
118             0.989658, 0.989685, 0.989713, 0.98974, 0.989768, 0.989795, 0.989822, 
119             0.989849, 0.989876, 0.989902, 0.989929, 0.989955, 0.989981, 0.990007, 
120             0.990033, 0.990059, 0.990085, 0.99011, 0.990136, 0.990161, 0.990186, 
121             0.990211, 0.990236, 0.990261, 0.990285, 0.99031, 0.990334, 0.990358, 
122             0.990383, 0.990407, 0.99043, 0.990454, 0.990478, 0.990501, 0.990525, 
123             0.990548, 0.990571, 0.990594, 0.990617, 0.99064, 0.990663, 0.990686, 
124             0.990708, 0.990731, 0.990753, 0.990775, 0.990797, 0.990819, 0.990841, 
125             0.990863, 0.990885, 0.990906, 0.990928, 0.990949, 0.99097, 0.990991, 
126             0.991013, 0.991034, 0.991054, 0.991075, 0.991096, 0.991116, 0.991137, 
127             0.991157, 0.991178, 0.991198, 0.991218, 0.991238, 0.991258, 0.991278, 
128             0.991298, 0.991317, 0.991337, 0.991356, 0.991376, 0.991395, 0.991414, 
129             0.991433, 0.991452, 0.991471, 0.99149, 0.991509, 0.991528, 0.991547, 
130             0.991565, 0.991584, 0.991602, 0.99162, 0.991639, 0.991657, 0.991675, 
131             0.991693, 0.991711, 0.991729, 0.991746, 0.991764, 0.991782, 0.991799, 
132             0.991817, 0.991834, 0.991851, 0.991869, 0.991886, 0.991903, 0.99192, 
133             0.991937, 0.991954, 0.991971, 0.991987, 0.992004, 0.992021, 0.992037, 
134             0.992054, 0.99207, 0.992086, 0.992103, 0.992119, 0.992135, 0.992151, 
135             0.992167, 0.992183, 0.992199, 0.992215, 0.99223, 0.992246, 0.992262, 
136             0.992277, 0.992293, 0.992308, 0.992324, 0.992339, 0.992354, 0.992369, 
137             0.992384, 0.9924, 0.992415, 0.992429, 0.992444, 0.992459, 0.992474, 0.992489, 
138             0.992503, 0.992518, 0.992533, 0.992547, 0.992561, 0.992576, 0.99259, 
139             0.992604, 0.992619, 0.992633, 0.992647, 0.992661, 0.992675, 0.992689, 
140             0.992703, 0.992717, 0.99273, 0.992744, 0.992758, 0.992771, 0.992785, 
141             0.992798, 0.992812, 0.992825, 0.992839, 0.992852, 0.992865, 0.992879, 
142             0.992892, 0.992905, 0.992918, 0.992931, 0.992944, 0.992957, 0.99297, 
143             0.992983, 0.992995, 0.993008, 0.993021, 0.993034, 0.993046, 0.993059, 
144             0.993071, 0.993084, 0.993096, 0.993109, 0.993121, 0.993133, 0.993145, 
145             0.993158, 0.99317, 0.993182, 0.993194, 0.993206, 0.993218, 0.99323, 0.993242, 
146             0.993254, 0.993266, 0.993277, 0.993289, 0.993301, 0.993312, 0.993324, 
147             0.993336, 0.993347, 0.993359, 0.99337, 0.993382, 0.993393, 0.993404, 
148             0.993416, 0.993427, 0.993438, 0.993449, 0.99346, 0.993472, 0.993483, 
149             0.993494, 0.993505, 0.993516, 0.993527, 0.993538, 0.993548, 0.993559, 
150             0.99357, 0.993581, 0.993591, 0.993602, 0.993613, 0.993623, 0.993634, 
151             0.993644, 0.993655, 0.993665, 0.993676, 0.993686, 0.993697, 0.993707, 
152             0.993717, 0.993727, 0.993738, 0.993748, 0.993758, 0.993768, 0.993778, 
153             0.993788, 0.993798, 0.993808, 0.993818, 0.993828, 0.993838, 0.993848, 
154             0.993858, 0.993868, 0.993877, 0.993887, 0.993897, 0.993907, 0.993916, 
155             0.993926, 0.993935, 0.993945, 0.993954, 0.993964, 0.993973, 0.993983, 
156             0.993992, 0.994002, 0.994011, 0.99402, 0.99403, 0.994039, 0.994048, 0.994057, 
157             0.994067, 0.994076, 0.994085, 0.994094, 0.994103, 0.994112, 0.994121, 
158             0.99413, 0.994139, 0.994148, 0.994157, 0.994166, 0.994175, 0.994183, 
159             0.994192, 0.994201, 0.99421, 0.994218, 0.994227, 0.994236, 0.994244, 
160             0.994253, 0.994262, 0.99427, 0.994279, 0.994287, 0.994296, 0.994304, 
161             0.994313, 0.994321, 0.994329, 0.994338, 0.994346, 0.994354, 0.994363, 
162             0.994371, 0.994379, 0.994387, 0.994395, 0.994404, 0.994412, 0.99442, 
163             0.994428, 0.994436, 0.994444, 0.994452, 0.99446, 0.994468, 0.994476, 
164             0.994484, 0.994492, 0.9945, 0.994508, 0.994516, 0.994523, 0.994531, 0.994539, 
165             0.994547, 0.994554, 0.994562, 0.99457, 0.994577, 0.994585, 0.994593, 0.9946, 
166             0.994608, 0.994615, 0.994623, 0.994631, 0.994638, 0.994645, 0.994653, 
167             0.99466, 0.994668, 0.994675, 0.994683, 0.99469, 0.994697, 0.994705, 0.994712, 
168             0.994719, 0.994726, 0.994734, 0.994741, 0.994748, 0.994755, 0.994762, 
169             0.994769, 0.994777, 0.994784, 0.994791, 0.994798, 0.994805, 0.994812, 
170             0.994819, 0.994826, 0.994833, 0.99484, 0.994847, 0.994854, 0.99486, 0.994867, 
171             0.994874, 0.994881, 0.994888, 0.994895, 0.994901, 0.994908, 0.994915, 
172             0.994922, 0.994928, 0.994935, 0.994942, 0.994948, 0.994955, 0.994962, 
173             0.994968, 0.994975, 0.994981, 0.994988, 0.994994, 0.995001, 0.995007, 
174             0.995014, 0.99502, 0.995027, 0.995033, 0.99504, 0.995046, 0.995052, 0.995059, 
175             0.995065, 0.995071, 0.995078, 0.995084, 0.99509, 0.995097, 0.995103, 
176             0.995109, 0.995115, 0.995121, 0.995128, 0.995134, 0.99514, 0.995146, 
177             0.995152, 0.995158, 0.995164, 0.995171, 0.995177, 0.995183, 0.995189, 
178             0.995195, 0.995201, 0.995207, 0.995213, 0.995219, 0.995225, 0.995231, 
179             0.995236, 0.995242, 0.995248, 0.995254, 0.99526, 0.995266, 0.995272, 
180             0.995277, 0.995283, 0.995289, 0.995295, 0.995301, 0.995306, 0.995312, 
181             0.995318, 0.995323, 0.995329, 0.995335, 0.99534, 0.995346, 0.995352, 
182             0.995357, 0.995363, 0.995369, 0.995374, 0.99538, 0.995385, 0.995391, 
183             0.995396, 0.995402, 0.995407, 0.995413, 0.995418, 0.995424, 0.995429, 
184             0.995435, 0.99544, 0.995445, 0.995451, 0.995456, 0.995462, 0.995467, 
185             0.995472, 0.995478, 0.995483, 0.995488, 0.995493, 0.995499, 0.995504, 
186             0.995509, 0.995515, 0.99552, 0.995525, 0.99553, 0.995535, 0.995541, 0.995546, 
187             0.995551, 0.995556, 0.995561, 0.995566, 0.995571, 0.995577, 0.995582, 
188             0.995587, 0.995592, 0.995597, 0.995602, 0.995607, 0.995612, 0.995617, 
189             0.995622, 0.995627, 0.995632, 0.995637, 0.995642, 0.995647, 0.995652, 
190             0.995657, 0.995661, 0.995666, 0.995671, 0.995676, 0.995681, 0.995686, 
191             0.995691, 0.995695, 0.9957, 0.995705, 0.99571, 0.995715, 0.995719, 0.995724, 
192             0.995729, 0.995734, 0.995738, 0.995743, 0.995748, 0.995753, 0.995757, 
193             0.995762, 0.995767, 0.995771, 0.995776, 0.995781, 0.995785, 0.99579, 
194             0.995794, 0.995799, 0.995804, 0.995808, 0.995813, 0.995817, 0.995822, 
195             0.995826, 0.995831, 0.995835, 0.99584, 0.995844, 0.995849, 0.995853, 
196             0.995858, 0.995862, 0.995867, 0.995871, 0.995876, 0.99588, 0.995885, 
197             0.995889, 0.995893, 0.995898, 0.995902, 0.995906, 0.995911, 0.995915, 
198             0.99592, 0.995924, 0.995928, 0.995932, 0.995937, 0.995941, 0.995945, 0.99595, 
199             0.995954, 0.995958, 0.995962, 0.995967, 0.995971, 0.995975, 0.995979, 
200             0.995984, 0.995988, 0.995992, 0.995996, 0.996, 0.996004, 0.996009, 0.996013, 
201             0.996017, 0.996021, 0.996025, 0.996029, 0.996033, 0.996037, 0.996041, 
202             0.996046, 0.99605, 0.996054, 0.996058, 0.996062, 0.996066, 0.99607, 0.996074, 
203             0.996078, 0.996082, 0.996086, 0.99609, 0.996094, 0.996098, 0.996102, 
204             0.996106, 0.99611, 0.996114, 0.996117, 0.996121, 0.996125, 0.996129, 
205             0.996133, 0.996137, 0.996141, 0.996145, 0.996149, 0.996152, 0.996156, 
206             0.99616, 0.996164]
207
208 # Run-time configuration parameters (can be set with command-line options)
209
210 $rerun=1
211 $inner=3
212 $warmup=1
213 $outer=4
214 $includeSunSpider=true
215 $includeV8=true
216 $includeKraken=true
217 $measureGC=false
218 $benchmarkPattern=nil
219 $verbosity=0
220 $timeMode=:preciseTime
221 $forceVMKind=nil
222 $brief=false
223 $silent=false
224 $remoteHosts=[]
225 $alsoLocal=false
226 $sshOptions=[]
227 $vms = []
228 $needToCopyVMs = false
229 $dontCopyVMs = false
230
231 $prepare = true
232 $run = true
233 $analyze = []
234
235 # Helpful functions and classes
236
237 def smallUsage
238   puts "Use the --help option to get basic usage information."
239   exit 1
240 end
241
242 def usage
243   puts "bencher [options] <vm1> [<vm2> ...]"
244   puts
245   puts "Runs one or more JavaScript runtimes against SunSpider, V8, and/or Kraken"
246   puts "benchmarks, and reports detailed statistics.  What makes bencher special is"
247   puts "that each benchmark/VM configuration is run in a single VM invocation, and"
248   puts "the invocations are run in random order.  This minimizes systematics due to"
249   puts "one benchmark polluting the running time of another.  The fine-grained"
250   puts "interleaving of VM invocations further minimizes systematics due to changes in"
251   puts "the performance or behavior of your machine."
252   puts 
253   puts "Bencher is highly configurable.  You can compare as many VMs as you like.  You"
254   puts "can change the amount of warm-up iterations, number of iterations executed per"
255   puts "VM invocation, and the number of VM invocations per benchmark.  By default,"
256   puts "SunSpider, VM, and Kraken are all run; but you can run any combination of these"
257   puts "suites."
258   puts
259   puts "The <vm> should be either a path to a JavaScript runtime executable (such as"
260   puts "jsc), or a string of the form <name>:<path>, where the <path> is the path to"
261   puts "the executable and <name> is the name that you would like to give the"
262   puts "configuration for the purposeof reporting.  If no name is given, a generic name"
263   puts "of the form Conf#<n> will be ascribed to the configuration automatically."
264   puts
265   puts "Options:"
266   puts "--rerun <n>          Set the number of iterations of the benchmark that"
267   puts "                     contribute to the measured run time.  Default is #{$rerun}."
268   puts "--inner <n>          Set the number of inner (per-runtime-invocation)"
269   puts "                     iterations.  Default is #{$inner}."
270   puts "--outer <n>          Set the number of runtime invocations for each benchmark."
271   puts "                     Default is #{$outer}."
272   puts "--warmup <n>         Set the number of warm-up runs per invocation.  Default"
273   puts "                     is #{$warmup}."
274   puts "--timing-mode        Set the way that bencher measures time.  Possible values"
275   puts "                     are 'preciseTime' and 'date'.  Default is 'preciseTime'."
276   puts "--force-vm-kind      Turn off auto-detection of VM kind, and assume that it is"
277   puts "                     the one specified.  Valid arguments are 'jsc' or"
278   puts "                     'DumpRenderTree'."
279   puts "--force-vm-copy      Force VM builds to be copied to bencher's working directory."
280   puts "                     This may reduce pathologies resulting from path names."
281   puts "--dont-copy-vms      Don't copy VMs even when doing a remote benchmarking run;"
282   puts "                     instead assume that they are already there."
283   puts "--v8-only            Only run V8."
284   puts "--sunspider-only     Only run SunSpider."
285   puts "--kraken-only        Only run Kraken."
286   puts "--exclude-v8         Exclude V8 (only run SunSpider and Kraken)."
287   puts "--exclude-sunspider  Exclude SunSpider (only run V8 and Kraken)."
288   puts "--exclude-kraken     Exclude Kraken (only run SunSpider and V8)."
289   puts "--benchmarks         Only run benchmarks matching the given regular expression."
290   puts "--measure-gc         Turn off manual calls to gc(), so that GC time is measured."
291   puts "                     Works best with large values of --inner.  You can also say"
292   puts "                     --measure-gc <conf>, which turns this on for one"
293   puts "                     configuration only."
294   puts "--verbose or -v      Print more stuff."
295   puts "--brief              Print only the final result for each VM."
296   puts "--silent             Don't print progress. This might slightly reduce some"
297   puts "                     performance perturbation."
298   puts "--remote <sshhosts>  Performance performance measurements remotely, on the given"
299   puts "                     SSH host(s). Easiest way to use this is to specify the SSH"
300   puts "                     user@host string. However, you can also supply a comma-"
301   puts "                     separated list of SSH hosts. Alternatively, you can use this"
302   puts "                     option multiple times to specify multiple hosts. This"
303   puts "                     automatically copies the WebKit release builds of the VMs"
304   puts "                     you specified to all of the hosts."
305   puts "--ssh-options        Pass additional options to SSH."
306   puts "--local              Also do a local benchmark run even when doing --remote."
307   puts "--prepare-only       Only prepare the bencher runscript (a shell script that"
308   puts "                     invokes the VMs to run benchmarks) but don't run it."
309   puts "--analyze            Only read the output of the runscript but don't do anything"
310   puts "                     else. This requires passing the same arguments to bencher"
311   puts "                     that you passed when running --prepare-only."
312   puts "--help or -h         Display this message."
313   puts
314   puts "Example:"
315   puts "bencher TipOfTree:/Volumes/Data/pizlo/OpenSource/WebKitBuild/Release/jsc MyChanges:/Volumes/Data/pizlo/secondary/OpenSource/WebKitBuild/Release/jsc"
316   exit 1
317 end
318
319 def fail(reason)
320   if reason.respond_to? :backtrace
321     puts "FAILED: #{reason}"
322     puts "Stack trace:"
323     puts reason.backtrace.join("\n")
324   else
325     puts "FAILED: #{reason}"
326   end
327   smallUsage
328 end
329
330 def quickFail(r1,r2)
331   $stderr.puts "#{$0}: #{r1}"
332   puts
333   fail(r2)
334 end
335
336 def intArg(argName,arg,min,max)
337   result=arg.to_i
338   unless result.to_s == arg
339     quickFail("Expected an integer value for #{argName}, but got #{arg}.",
340               "Invalid argument for command-line option")
341   end
342   if min and result<min
343     quickFail("Argument for #{argName} cannot be smaller than #{min}.",
344               "Invalid argument for command-line option")
345   end
346   if max and result>max
347     quickFail("Argument for #{argName} cannot be greater than #{max}.",
348               "Invalid argument for command-line option")
349   end
350   result
351 end
352
353 def computeMean(array)
354   sum=0.0
355   array.each {
356     | value |
357     sum += value
358   }
359   sum/array.length
360 end
361
362 def computeGeometricMean(array)
363   mult=1.0
364   array.each {
365     | value |
366     mult*=value
367   }
368   mult**(1.0/array.length)
369 end
370
371 def computeHarmonicMean(array)
372   1.0 / computeMean(array.collect{ | value | 1.0 / value })
373 end
374
375 def computeStdDev(array)
376   case array.length
377   when 0
378     0.0/0.0
379   when 1
380     0.0
381   else
382     begin
383       mean=computeMean(array)
384       sum=0.0
385       array.each {
386         | value |
387         sum += (value-mean)**2
388       }
389       Math.sqrt(sum/(array.length-1))
390     rescue
391       0.0/0.0
392     end
393   end
394 end
395
396 class Array
397   def shuffle!
398     size.downto(1) { |n| push delete_at(rand(n)) }
399     self
400   end
401 end
402
403 def inverseBetaRegularized(n)
404   IBR_LOOKUP[n-1]
405 end
406
407 def numToStr(num)
408   "%.4f"%(num.to_f)
409 end
410   
411 class NoChange
412   attr_reader :amountFaster
413   
414   def initialize(amountFaster)
415     @amountFaster = amountFaster
416   end
417   
418   def shortForm
419     " "
420   end
421   
422   def longForm
423     "  might be #{numToStr(@amountFaster)}x faster"
424   end
425   
426   def to_s
427     if @amountFaster < 1.01
428       ""
429     else
430       longForm
431     end
432   end
433 end
434
435 class Faster
436   attr_reader :amountFaster
437   
438   def initialize(amountFaster)
439     @amountFaster = amountFaster
440   end
441   
442   def shortForm
443     "^"
444   end
445   
446   def longForm
447     "^ definitely #{numToStr(@amountFaster)}x faster"
448   end
449   
450   def to_s
451     longForm
452   end
453 end
454
455 class Slower
456   attr_reader :amountSlower
457   
458   def initialize(amountSlower)
459     @amountSlower = amountSlower
460   end
461   
462   def shortForm
463     "!"
464   end
465   
466   def longForm
467     "! definitely #{numToStr(@amountSlower)}x slower"
468   end
469   
470   def to_s
471     longForm
472   end
473 end
474
475 class MayBeSlower
476   attr_reader :amountSlower
477   
478   def initialize(amountSlower)
479     @amountSlower = amountSlower
480   end
481   
482   def shortForm
483     "?"
484   end
485   
486   def longForm
487     "? might be #{numToStr(@amountSlower)}x slower"
488   end
489   
490   def to_s
491     if @amountSlower < 1.01
492       "?"
493     else
494       longForm
495     end
496   end
497 end
498
499 class Stats
500   def initialize
501     @array = []
502   end
503   
504   def add(value)
505     if value.is_a? Stats
506       add(value.array)
507     elsif value.respond_to? :each
508       value.each {
509         | v |
510         add(v)
511       }
512     else
513       @array << value.to_f
514     end
515   end
516     
517   def array
518     @array
519   end
520   
521   def sum
522     result=0
523     @array.each {
524       | value |
525       result += value
526     }
527     result
528   end
529   
530   def min
531     @array.min
532   end
533   
534   def max
535     @array.max
536   end
537   
538   def size
539     @array.length
540   end
541   
542   def mean
543     computeMean(array)
544   end
545   
546   def arithmeticMean
547     mean
548   end
549   
550   def stdDev
551     computeStdDev(array)
552   end
553
554   def stdErr
555     stdDev/Math.sqrt(size)
556   end
557   
558   # Computes a 95% Student's t distribution confidence interval
559   def confInt
560     if size < 2
561       0.0/0.0
562     else
563       raise if size > 1000
564       Math.sqrt(size-1.0)*stdErr*Math.sqrt(-1.0+1.0/inverseBetaRegularized(size-1))
565     end
566   end
567   
568   def lower
569     mean-confInt
570   end
571   
572   def upper
573     mean+confInt
574   end
575   
576   def geometricMean
577     computeGeometricMean(array)
578   end
579   
580   def harmonicMean
581     computeHarmonicMean(array)
582   end
583   
584   def compareTo(other)
585     if upper < other.lower
586       Faster.new(other.mean/mean)
587     elsif lower > other.upper
588       Slower.new(mean/other.mean)
589     elsif mean > other.mean
590       MayBeSlower.new(mean/other.mean)
591     else
592       NoChange.new(other.mean/mean)
593     end
594   end
595   
596   def to_s
597     "size = #{size}, mean = #{mean}, stdDev = #{stdDev}, stdErr = #{stdErr}, confInt = #{confInt}"
598   end
599 end
600
601 def doublePuts(out1,out2,msg)
602   out1.puts "#{out2.path}: #{msg}" if $verbosity>=3
603   out2.puts msg
604 end
605
606 class Benchfile < File
607   @@counter = 0
608   
609   attr_reader :filename, :basename
610   
611   def initialize(name)
612     @basename, @filename = Benchfile.uniqueFilename(name)
613     super(@filename, "w")
614   end
615   
616   def self.uniqueFilename(name)
617     if name.is_a? Array
618       basename = name[0] + @@counter.to_s + name[1]
619     else
620       basename = name + @@counter.to_s
621     end
622     filename = BENCH_DATA_PATH + "/" + basename
623     @@counter += 1
624     raise "Benchfile #{filename} already exists" if FileTest.exist?(filename)
625     [basename, filename]
626   end
627   
628   def self.create(name)
629     file = Benchfile.new(name)
630     yield file
631     file.close
632     file.basename
633   end
634 end
635
636 $dataFiles={}
637 def ensureFile(key, filename)
638   unless $dataFiles[key]
639     $dataFiles[key] = Benchfile.create(key) {
640       | outp |
641       doublePuts($stderr,outp,IO::read(filename))
642     }
643   end
644   $dataFiles[key]
645 end
646   
647 def emitBenchRunCodeFile(name, plan, benchDataPath, benchPath)
648   case plan.vm.vmType
649   when :jsc
650     Benchfile.create("bencher") {
651       | file |
652       case $timeMode
653       when :preciseTime
654         doublePuts($stderr,file,"function __bencher_curTimeMS() {")
655         doublePuts($stderr,file,"   return preciseTime()*1000")
656         doublePuts($stderr,file,"}")
657       when :date
658         doublePuts($stderr,file,"function __bencher_curTimeMS() {")
659         doublePuts($stderr,file,"   return Date.now()")
660         doublePuts($stderr,file,"}")
661       else
662         raise
663       end
664       
665       if benchDataPath
666         doublePuts($stderr,file,"load(#{benchDataPath.inspect});")
667         doublePuts($stderr,file,"gc();")
668         doublePuts($stderr,file,"for (var __bencher_index = 0; __bencher_index < #{$warmup+$inner}; ++__bencher_index) {")
669         doublePuts($stderr,file,"   before = __bencher_curTimeMS();")
670         $rerun.times {
671           doublePuts($stderr,file,"   load(#{benchPath.inspect});")
672         }
673         doublePuts($stderr,file,"   after = __bencher_curTimeMS();")
674         doublePuts($stderr,file,"   if (__bencher_index >= #{$warmup}) print(\"#{name}: #{plan.vm}: #{plan.iteration}: \" + (__bencher_index - #{$warmup}) + \": Time: \"+(after-before));");
675         doublePuts($stderr,file,"   gc();") unless plan.vm.shouldMeasureGC
676         doublePuts($stderr,file,"}")
677       else
678         doublePuts($stderr,file,"function __bencher_run(__bencher_what) {")
679         doublePuts($stderr,file,"   var __bencher_before = __bencher_curTimeMS();")
680         $rerun.times {
681           doublePuts($stderr,file,"   run(__bencher_what);")
682         }
683         doublePuts($stderr,file,"   var __bencher_after = __bencher_curTimeMS();")
684         doublePuts($stderr,file,"   return __bencher_after - __bencher_before;")
685         doublePuts($stderr,file,"}")
686         $warmup.times {
687           doublePuts($stderr,file,"__bencher_run(#{benchPath.inspect})")
688           doublePuts($stderr,file,"gc();") unless plan.vm.shouldMeasureGC
689         }
690         $inner.times {
691           | innerIndex |
692           doublePuts($stderr,file,"print(\"#{name}: #{plan.vm}: #{plan.iteration}: #{innerIndex}: Time: \"+__bencher_run(#{benchPath.inspect}));")
693           doublePuts($stderr,file,"gc();") unless plan.vm.shouldMeasureGC
694         }
695       end
696     }
697   when :dumpRenderTree
698     mainCode = Benchfile.create("bencher") {
699       | file |
700       doublePuts($stderr,file,"__bencher_count = 0;")
701       doublePuts($stderr,file,"function __bencher_doNext(result) {")
702       doublePuts($stderr,file,"    if (__bencher_count >= #{$warmup})")
703       doublePuts($stderr,file,"        debug(\"#{name}: #{plan.vm}: #{plan.iteration}: \" + (__bencher_count - #{$warmup}) + \": Time: \" + result);")
704       doublePuts($stderr,file,"    __bencher_count++;")
705       doublePuts($stderr,file,"    if (__bencher_count < #{$inner+$warmup})")
706       doublePuts($stderr,file,"        __bencher_runImpl(__bencher_doNext);")
707       doublePuts($stderr,file,"    else")
708       doublePuts($stderr,file,"        quit();")
709       doublePuts($stderr,file,"}")
710       doublePuts($stderr,file,"__bencher_runImpl(__bencher_doNext);")
711     }
712     
713     cssCode = Benchfile.create("bencher-css") {
714       | file |
715       doublePuts($stderr,file,".pass {\n    font-weight: bold;\n    color: green;\n}\n.fail {\n    font-weight: bold;\n    color: red;\n}\n\#console {\n    white-space: pre-wrap;\n    font-family: monospace;\n}")
716     }
717     
718     preCode = Benchfile.create("bencher-pre") {
719       | file |
720       doublePuts($stderr,file,"if (window.testRunner) {")
721       doublePuts($stderr,file,"    testRunner.dumpAsText(window.enablePixelTesting);")
722       doublePuts($stderr,file,"    testRunner.waitUntilDone();")
723       doublePuts($stderr,file,"}")
724       doublePuts($stderr,file,"")
725       doublePuts($stderr,file,"function debug(msg)")
726       doublePuts($stderr,file,"{")
727       doublePuts($stderr,file,"    var span = document.createElement(\"span\");")
728       doublePuts($stderr,file,"    document.getElementById(\"console\").appendChild(span); // insert it first so XHTML knows the namespace")
729       doublePuts($stderr,file,"    span.innerHTML = msg + '<br />';")
730       doublePuts($stderr,file,"}")
731       doublePuts($stderr,file,"")
732       doublePuts($stderr,file,"function quit() {")
733       doublePuts($stderr,file,"    testRunner.notifyDone();")
734       doublePuts($stderr,file,"}")
735       doublePuts($stderr,file,"")
736       doublePuts($stderr,file,"__bencher_continuation=null;")
737       doublePuts($stderr,file,"")
738       doublePuts($stderr,file,"function reportResult(result) {")
739       doublePuts($stderr,file,"    __bencher_continuation(result);")
740       doublePuts($stderr,file,"}")
741       doublePuts($stderr,file,"")
742       doublePuts($stderr,file,"function __bencher_runImpl(continuation) {")
743       doublePuts($stderr,file,"    function doit() {")
744       doublePuts($stderr,file,"        document.getElementById(\"frameparent\").innerHTML = \"\";")
745       doublePuts($stderr,file,"        document.getElementById(\"frameparent\").innerHTML = \"<iframe id='testframe'>\";")
746       doublePuts($stderr,file,"        var testFrame = document.getElementById(\"testframe\");")
747       doublePuts($stderr,file,"        testFrame.contentDocument.open();")
748       doublePuts($stderr,file,"        testFrame.contentDocument.write(\"<!DOCTYPE html>\\n<head></head><body><div id=\\\"console\\\"></div>\");")
749       if benchDataPath
750         doublePuts($stderr,file,"        testFrame.contentDocument.write(\"<script src=\\\"#{benchDataPath}\\\"></script>\");")
751       end
752       doublePuts($stderr,file,"        testFrame.contentDocument.write(\"<script type=\\\"text/javascript\\\">__bencher_before = Date.now();</script><script src=\\\"#{benchPath}\\\"></script><script type=\\\"text/javascript\\\">window.parent.reportResult(Date.now() - __bencher_before);</script></body></html>\");")
753       doublePuts($stderr,file,"        testFrame.contentDocument.close();")
754       doublePuts($stderr,file,"    }")
755       doublePuts($stderr,file,"    __bencher_continuation = continuation;")
756       doublePuts($stderr,file,"    window.setTimeout(doit, 10);")
757       doublePuts($stderr,file,"}")
758     }
759
760     Benchfile.create(["bencher-htmldoc",".html"]) {
761       | file |
762       doublePuts($stderr,file,"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n<html><head><link rel=\"stylesheet\" href=\"#{cssCode}\"><script src=\"#{preCode}\"></script></head><body><div id=\"console\"></div><div id=\"frameparent\"></div><script src=\"#{mainCode}\"></script></body></html>")
763     }
764   else
765     raise
766   end
767 end
768
769 def emitBenchRunCode(name, plan, benchDataPath, benchPath)
770   plan.vm.emitRunCode(emitBenchRunCodeFile(name, plan, benchDataPath, benchPath))
771 end
772
773 def planForDescription(plans, benchFullname, vmName, iteration)
774   raise unless benchFullname =~ /\//
775   suiteName = $~.pre_match
776   benchName = $~.post_match
777   result = plans.select{|v| v.suite.name == suiteName and v.benchmark.name == benchName and v.vm.name == vmName and v.iteration == iteration}
778   raise unless result.size == 1
779   result[0]
780 end
781
782 class ParsedResult
783   attr_reader :plan, :innerIndex, :time
784   
785   def initialize(plan, innerIndex, time)
786     @plan = plan
787     @innerIndex = innerIndex
788     @time = time
789     
790     raise unless @plan.is_a? BenchPlan
791     raise unless @innerIndex.is_a? Integer
792     raise unless @time.is_a? Numeric
793   end
794   
795   def benchmark
796     plan.benchmark
797   end
798   
799   def suite
800     plan.suite
801   end
802   
803   def vm
804     plan.vm
805   end
806   
807   def outerIndex
808     plan.iteration
809   end
810   
811   def self.parse(plans, string)
812     if string =~ /([a-zA-Z0-9\/-]+): ([a-zA-Z0-9_# ]+): ([0-9]+): ([0-9]+): Time: /
813       benchFullname = $1
814       vmName = $2
815       outerIndex = $3.to_i
816       innerIndex = $4.to_i
817       time = $~.post_match.to_f
818       ParsedResult.new(planForDescription(plans, benchFullname, vmName, outerIndex), innerIndex, time)
819     else
820       nil
821     end
822   end
823 end
824
825 class VM
826   def initialize(origPath, name, nameKind, svnRevision)
827     @origPath = origPath.to_s
828     @path = origPath.to_s
829     @name = name
830     @nameKind = nameKind
831     
832     if $forceVMKind
833       @vmType = $forceVMKind
834     else
835       if @origPath =~ /DumpRenderTree$/
836         @vmType = :dumpRenderTree
837       else
838         @vmType = :jsc
839       end
840     end
841     
842     @svnRevision = svnRevision
843     
844     # Try to detect information about the VM.
845     if path =~ /\/WebKitBuild\/Release\/([a-zA-Z]+)$/
846       @checkoutPath = $~.pre_match
847       # FIXME: Use some variant of this: 
848       # <bdash>   def retrieve_revision
849       # <bdash>     `perl -I#{@path}/Tools/Scripts -MVCSUtils -e 'print svnRevisionForDirectory("#{@path}");'`.to_i
850       # <bdash>   end
851       unless @svnRevision
852         begin
853           Dir.chdir(@checkoutPath) {
854             $stderr.puts ">> cd #{@checkoutPath} && svn info" if $verbosity>=2
855             IO.popen("svn info", "r") {
856               | inp |
857               inp.each_line {
858                 | line |
859                 if line =~ /Revision: ([0-9]+)/
860                   @svnRevision = $1
861                 end
862               }
863             }
864           }
865           unless @svnRevision
866             $stderr.puts "Warning: running svn info for #{name} silently failed."
867           end
868         rescue => e
869           # Failed to detect svn revision.
870           $stderr.puts "Warning: could not get svn revision information for #{name}: #{e}"
871         end
872       end
873     else
874       $stderr.puts "Warning: could not identify checkout location for #{name}"
875     end
876     
877     if @path =~ /\/Release\/([a-zA-Z]+)$/
878       @libPath, @relativeBinPath = $~.pre_match+"/Release", "./#{$1}"
879     elsif @path =~ /\/Contents\/Resources\/([a-zA-Z]+)$/
880       @libPath = $~.pre_match
881     elsif @path =~ /\/JavaScriptCore.framework\/Resources\/([a-zA-Z]+)$/
882       @libPath, @relativeBinPath = $~.pre_match, $&[1..-1]
883     end
884   end
885   
886   def canCopyIntoBenchPath
887     if @libPath and @relativeBinPath
888       true
889     else
890       false
891     end
892   end
893   
894   def copyIntoBenchPath
895     raise unless canCopyIntoBenchPath
896     basename, filename = Benchfile.uniqueFilename("vm")
897     raise unless Dir.mkdir(filename)
898     cmd = "cp -a #{@libPath.inspect}/* #{filename.inspect}"
899     $stderr.puts ">> #{cmd}" if $verbosity>=2
900     raise unless system(cmd)
901     @path = "#{basename}/#{@relativeBinPath}"
902     @libPath = basename
903   end
904   
905   def to_s
906     @name
907   end
908   
909   def name
910     @name
911   end
912   
913   def shouldMeasureGC
914     $measureGC == true or ($measureGC == name)
915   end
916   
917   def origPath
918     @origPath
919   end
920   
921   def path
922     @path
923   end
924   
925   def nameKind
926     @nameKind
927   end
928   
929   def vmType
930     @vmType
931   end
932   
933   def checkoutPath
934     @checkoutPath
935   end
936   
937   def svnRevision
938     @svnRevision
939   end
940   
941   def printFunction
942     case @vmType
943     when :jsc
944       "print"
945     when :dumpRenderTree
946       "debug"
947     else
948       raise @vmType
949     end
950   end
951   
952   def emitRunCode(fileToRun)
953     myLibPath = @libPath
954     myLibPath = "" unless myLibPath
955     $script.puts "export DYLD_LIBRARY_PATH=#{myLibPath.to_s.inspect}"
956     $script.puts "export DYLD_FRAMEWORK_PATH=#{myLibPath.to_s.inspect}"
957     $script.puts "#{path} #{fileToRun}"
958   end
959 end
960
961 class StatsAccumulator
962   def initialize
963     @stats = []
964     ($outer*$inner).times {
965       @stats << Stats.new
966     }
967   end
968   
969   def statsForIteration(outerIteration, innerIteration)
970     @stats[outerIteration*$inner + innerIteration]
971   end
972   
973   def stats
974     result = Stats.new
975     @stats.each {
976       | stat |
977       result.add(yield stat)
978     }
979     result
980   end
981   
982   def geometricMeanStats
983     stats {
984       | stat |
985       stat.geometricMean
986     }
987   end
988   
989   def arithmeticMeanStats
990     stats {
991       | stat |
992       stat.arithmeticMean
993     }
994   end
995 end
996
997 module Benchmark
998   attr_accessor :benchmarkSuite
999   attr_reader :name
1000   
1001   def fullname
1002     benchmarkSuite.name + "/" + name
1003   end
1004   
1005   def to_s
1006     fullname
1007   end
1008 end
1009
1010 class SunSpiderBenchmark
1011   include Benchmark
1012   
1013   def initialize(name)
1014     @name = name
1015   end
1016   
1017   def emitRunCode(plan)
1018     emitBenchRunCode(fullname, plan, nil, ensureFile("SunSpider-#{@name}", "#{SUNSPIDER_PATH}/#{@name}.js"))
1019   end
1020 end
1021
1022 class V8Benchmark
1023   include Benchmark
1024   
1025   def initialize(name)
1026     @name = name
1027   end
1028   
1029   def emitRunCode(plan)
1030     emitBenchRunCode(fullname, plan, nil, ensureFile("V8-#{@name}", "#{V8_PATH}/v8-#{@name}.js"))
1031   end
1032 end
1033
1034 class KrakenBenchmark
1035   include Benchmark
1036   
1037   def initialize(name)
1038     @name = name
1039   end
1040   
1041   def emitRunCode(plan)
1042     emitBenchRunCode(fullname, plan, ensureFile("KrakenData-#{@name}", "#{KRAKEN_PATH}/#{@name}-data.js"), ensureFile("Kraken-#{@name}", "#{KRAKEN_PATH}/#{@name}.js"))
1043   end
1044 end
1045
1046 class BenchmarkSuite
1047   def initialize(name, path, preferredMean)
1048     @name = name
1049     @path = path
1050     @preferredMean = preferredMean
1051     @benchmarks = []
1052   end
1053   
1054   def name
1055     @name
1056   end
1057   
1058   def to_s
1059     @name
1060   end
1061   
1062   def path
1063     @path
1064   end
1065   
1066   def add(benchmark)
1067     if not $benchmarkPattern or "#{@name}/#{benchmark.name}" =~ $benchmarkPattern
1068       benchmark.benchmarkSuite = self
1069       @benchmarks << benchmark
1070     end
1071   end
1072   
1073   def benchmarks
1074     @benchmarks
1075   end
1076   
1077   def benchmarkForName(name)
1078     result = @benchmarks.select{|v| v.name == name}
1079     raise unless result.length == 1
1080     result[0]
1081   end
1082   
1083   def empty?
1084     @benchmarks.empty?
1085   end
1086   
1087   def retain_if
1088     @benchmarks.delete_if {
1089       | benchmark |
1090       not yield benchmark
1091     }
1092   end
1093   
1094   def preferredMean
1095     @preferredMean
1096   end
1097   
1098   def computeMean(stat)
1099     stat.send @preferredMean
1100   end
1101 end
1102
1103 class BenchRunPlan
1104   def initialize(benchmark, vm, iteration)
1105     @benchmark = benchmark
1106     @vm = vm
1107     @iteration = iteration
1108   end
1109   
1110   def benchmark
1111     @benchmark
1112   end
1113   
1114   def suite
1115     @benchmark.benchmarkSuite
1116   end
1117   
1118   def vm
1119     @vm
1120   end
1121   
1122   def iteration
1123     @iteration
1124   end
1125   
1126   def emitRunCode
1127     @benchmark.emitRunCode(self)
1128   end
1129 end
1130
1131 class BenchmarkOnVM
1132   def initialize(benchmark, suiteOnVM)
1133     @benchmark = benchmark
1134     @suiteOnVM = suiteOnVM
1135     @stats = Stats.new
1136   end
1137   
1138   def to_s
1139     "#{@benchmark} on #{@suiteOnVM.vm}"
1140   end
1141   
1142   def benchmark
1143     @benchmark
1144   end
1145   
1146   def vm
1147     @suiteOnVM.vm
1148   end
1149   
1150   def vmStats
1151     @suiteOnVM.vmStats
1152   end
1153   
1154   def suite
1155     @benchmark.benchmarkSuite
1156   end
1157   
1158   def suiteOnVM
1159     @suiteOnVM
1160   end
1161   
1162   def stats
1163     @stats
1164   end
1165   
1166   def parseResult(result)
1167     raise "VM mismatch; I've got #{vm} and they've got #{result.vm}" unless result.vm == vm
1168     raise unless result.benchmark == @benchmark
1169     @stats.add(result.time)
1170   end
1171 end
1172
1173 class SuiteOnVM < StatsAccumulator
1174   def initialize(vm, vmStats, suite)
1175     super()
1176     @vm = vm
1177     @vmStats = vmStats
1178     @suite = suite
1179     
1180     raise unless @vm.is_a? VM
1181     raise unless @vmStats.is_a? StatsAccumulator
1182     raise unless @suite.is_a? BenchmarkSuite
1183   end
1184   
1185   def to_s
1186     "#{@suite} on #{@vm}"
1187   end
1188   
1189   def suite
1190     @suite
1191   end
1192   
1193   def vm
1194     @vm
1195   end
1196   
1197   def vmStats
1198     raise unless @vmStats
1199     @vmStats
1200   end
1201 end
1202
1203 class BenchPlan
1204   def initialize(benchmarkOnVM, iteration)
1205     @benchmarkOnVM = benchmarkOnVM
1206     @iteration = iteration
1207   end
1208   
1209   def to_s
1210     "#{@benchmarkOnVM} \##{@iteration+1}"
1211   end
1212   
1213   def benchmarkOnVM
1214     @benchmarkOnVM
1215   end
1216   
1217   def benchmark
1218     @benchmarkOnVM.benchmark
1219   end
1220   
1221   def suite
1222     @benchmarkOnVM.suite
1223   end
1224   
1225   def vm
1226     @benchmarkOnVM.vm
1227   end
1228   
1229   def iteration
1230     @iteration
1231   end
1232   
1233   def parseResult(result)
1234     raise unless result.plan == self
1235     @benchmarkOnVM.parseResult(result)
1236     @benchmarkOnVM.vmStats.statsForIteration(@iteration, result.innerIndex).add(result.time)
1237     @benchmarkOnVM.suiteOnVM.statsForIteration(@iteration, result.innerIndex).add(result.time)
1238   end
1239 end
1240
1241 def lpad(str,chars)
1242   if str.length>chars
1243     str
1244   else
1245     "%#{chars}s"%(str)
1246   end
1247 end
1248
1249 def rpad(str,chars)
1250   while str.length<chars
1251     str+=" "
1252   end
1253   str
1254 end
1255
1256 def center(str,chars)
1257   while str.length<chars
1258     str+=" "
1259     if str.length<chars
1260       str=" "+str
1261     end
1262   end
1263   str
1264 end
1265
1266 def statsToStr(stats)
1267   if $inner*$outer == 1
1268     string = numToStr(stats.mean)
1269     raise unless string =~ /\./
1270     left = $~.pre_match
1271     right = $~.post_match
1272     lpad(left,12)+"."+rpad(right,9)
1273   else
1274     lpad(numToStr(stats.mean),11)+"+-"+rpad(numToStr(stats.confInt),9)
1275   end
1276 end
1277
1278 def plural(num)
1279   if num == 1
1280     ""
1281   else
1282     "s"
1283   end
1284 end
1285
1286 def wrap(str, columns)
1287   array = str.split
1288   result = ""
1289   curLine = array.shift
1290   array.each {
1291     | curStr |
1292     if (curLine + " " + curStr).size > columns
1293       result += curLine + "\n"
1294       curLine = curStr
1295     else
1296       curLine += " " + curStr
1297     end
1298   }
1299   result + curLine + "\n"
1300 end
1301   
1302 def runAndGetResults
1303   results = nil
1304   Dir.chdir(BENCH_DATA_PATH) {
1305     IO.popen("sh ./runscript", "r") {
1306       | inp |
1307       results = inp.read
1308     }
1309     raise "Script did not complete correctly: #{$?}" unless $?.success?
1310   }
1311   raise unless results
1312   results
1313 end
1314
1315 def parseAndDisplayResults(results)
1316   vmStatses = []
1317   $vms.each {
1318     vmStatses << StatsAccumulator.new
1319   }
1320   
1321   suitesOnVMs = []
1322   suitesOnVMsForSuite = {}
1323   $suites.each {
1324     | suite |
1325     suitesOnVMsForSuite[suite] = []
1326   }
1327   suitesOnVMsForVM = {}
1328   $vms.each {
1329     | vm |
1330     suitesOnVMsForVM[vm] = []
1331   }
1332   
1333   benchmarksOnVMs = []
1334   benchmarksOnVMsForBenchmark = {}
1335   $benchmarks.each {
1336     | benchmark |
1337     benchmarksOnVMsForBenchmark[benchmark] = []
1338   }
1339   
1340   $vms.each_with_index {
1341     | vm, vmIndex |
1342     vmStats = vmStatses[vmIndex]
1343     $suites.each {
1344       | suite |
1345       suiteOnVM = SuiteOnVM.new(vm, vmStats, suite)
1346       suitesOnVMs << suiteOnVM
1347       suitesOnVMsForSuite[suite] << suiteOnVM
1348       suitesOnVMsForVM[vm] << suiteOnVM
1349       suite.benchmarks.each {
1350         | benchmark |
1351         benchmarkOnVM = BenchmarkOnVM.new(benchmark, suiteOnVM)
1352         benchmarksOnVMs << benchmarkOnVM
1353         benchmarksOnVMsForBenchmark[benchmark] << benchmarkOnVM
1354       }
1355     }
1356   }
1357   
1358   plans = []
1359   benchmarksOnVMs.each {
1360     | benchmarkOnVM |
1361     $outer.times {
1362       | iteration |
1363       plans << BenchPlan.new(benchmarkOnVM, iteration)
1364     }
1365   }
1366
1367   hostname = nil
1368   hwmodel = nil
1369   results.each_line {
1370     | line |
1371     line.chomp!
1372     if line =~ /HOSTNAME:([^.]+)/
1373       hostname = $1
1374     elsif line =~ /HARDWARE:hw\.model: /
1375       hwmodel = $~.post_match.chomp
1376     else
1377       result = ParsedResult.parse(plans, line.chomp)
1378       if result
1379         result.plan.parseResult(result)
1380       end
1381     end
1382   }
1383   
1384   # Compute the geomean of the preferred means of results on a SuiteOnVM
1385   overallResults = []
1386   $vms.each {
1387     | vm |
1388     result = Stats.new
1389     $outer.times {
1390       | outerIndex |
1391       $inner.times {
1392         | innerIndex |
1393         curResult = Stats.new
1394         suitesOnVMsForVM[vm].each {
1395           | suiteOnVM |
1396           # For a given iteration, suite, and VM, compute the suite's preferred mean
1397           # over the data collected for all benchmarks in that suite. We'll have one
1398           # sample per benchmark. For example on V8 this will be the geomean of 1
1399           # sample for crypto, 1 sample for deltablue, and so on, and 1 sample for
1400           # splay.
1401           curResult.add(suiteOnVM.suite.computeMean(suiteOnVM.statsForIteration(outerIndex, innerIndex)))
1402         }
1403         
1404         # curResult now holds 1 sample for each of the means computed in the above
1405         # loop. Compute the geomean over this, and store it.
1406         result.add(curResult.geometricMean)
1407       }
1408     }
1409
1410     # $overallResults will have a Stats for each VM. That Stats object will hold
1411     # $inner*$outer geomeans, allowing us to compute the arithmetic mean and
1412     # confidence interval of the geomeans of preferred means. Convoluted, but
1413     # useful and probably sound.
1414     overallResults << result
1415   }
1416   
1417   if $verbosity >= 2
1418     benchmarksOnVMs.each {
1419       | benchmarkOnVM |
1420       $stderr.puts "#{benchmarkOnVM}: #{benchmarkOnVM.stats}"
1421     }
1422     
1423     $vms.each_with_index {
1424       | vm, vmIndex |
1425       vmStats = vmStatses[vmIndex]
1426       $stderr.puts "#{vm} (arithmeticMean): #{vmStats.arithmeticMeanStats}"
1427       $stderr.puts "#{vm} (geometricMean): #{vmStats.geometricMeanStats}"
1428     }
1429   end
1430
1431   reportName =
1432     (if ($vms.collect {
1433            | vm |
1434            vm.nameKind
1435          }.index :auto)
1436        ""
1437      else
1438        $vms.collect {
1439          | vm |
1440          vm.to_s
1441        }.join("_") + "_"
1442      end) +
1443     ($suites.collect {
1444        | suite |
1445        suite.to_s
1446      }.join("")) + "_" +
1447     (if hostname
1448        hostname + "_"
1449      else
1450        ""
1451      end)+
1452     (begin
1453        time = Time.now
1454        "%04d%02d%02d_%02d%02d" %
1455          [ time.year, time.month, time.day,
1456            time.hour, time.min ]
1457      end) +
1458     "_benchReport.txt"
1459
1460   unless $brief
1461     puts "Generating benchmark report at #{reportName}"
1462   end
1463   
1464   outp = $stdout
1465   begin
1466     outp = File.open(reportName,"w")
1467   rescue => e
1468     $stderr.puts "Error: could not save report to #{reportName}: #{e}"
1469     $stderr.puts
1470   end
1471   
1472   def createVMsString
1473     result = ""
1474     result += "   " if $suites.size > 1
1475     result += rpad("", $benchpad)
1476     result += " "
1477     $vms.size.times {
1478       | index |
1479       if index != 0
1480         result += " "+NoChange.new(0).shortForm
1481       end
1482       result += lpad(center($vms[index].name, 9+9+2), 11+9+2)
1483     }
1484     result += "    "
1485     if $vms.size >= 3
1486       result += center("#{$vms[-1].name} v. #{$vms[0].name}",26)
1487     elsif $vms.size >= 2
1488       result += " "*26
1489     end
1490     result
1491   end
1492   
1493   columns = [createVMsString.size, 78].max
1494   
1495   outp.print "Benchmark report for "
1496   if $suites.size == 1
1497     outp.print $suites[0].to_s
1498   elsif $suites.size == 2
1499     outp.print "#{$suites[0]} and #{$suites[1]}"
1500   else
1501     outp.print "#{$suites[0..-2].join(', ')}, and #{$suites[-1]}"
1502   end
1503   if hostname
1504     outp.print " on #{hostname}"
1505   end
1506   if hwmodel
1507     outp.print " (#{hwmodel})"
1508   end
1509   outp.puts "."
1510   outp.puts
1511   
1512   # This looks stupid; revisit later.
1513   if false
1514     $suites.each {
1515       | suite |
1516       outp.puts "#{suite} at #{suite.path}"
1517     }
1518     
1519     outp.puts
1520   end
1521   
1522   outp.puts "VMs tested:"
1523   $vms.each {
1524     | vm |
1525     outp.print "\"#{vm.name}\" at #{vm.origPath}"
1526     if vm.svnRevision
1527       outp.print " (r#{vm.svnRevision})"
1528     end
1529     outp.puts
1530   }
1531   
1532   outp.puts
1533   
1534   outp.puts wrap("Collected #{$outer*$inner} sample#{plural($outer*$inner)} per benchmark/VM, "+
1535                  "with #{$outer} VM invocation#{plural($outer)} per benchmark."+
1536                  (if $rerun > 1 then (" Ran #{$rerun} benchmark iterations, and measured the "+
1537                                       "total time of those iterations, for each sample.")
1538                   else "" end)+
1539                  (if $measureGC == true then (" No manual garbage collection invocations were "+
1540                                               "emitted.")
1541                   elsif $measureGC then (" Emitted a call to gc() between sample measurements for "+
1542                                          "all VMs except #{$measureGC}.")
1543                   else (" Emitted a call to gc() between sample measurements.") end)+
1544                  (if $warmup == 0 then (" Did not include any warm-up iterations; measurements "+
1545                                         "began with the very first iteration.")
1546                   else (" Used #{$warmup*$rerun} benchmark iteration#{plural($warmup*$rerun)} per VM "+
1547                         "invocation for warm-up.") end)+
1548                  (case $timeMode
1549                   when :preciseTime then (" Used the jsc-specific preciseTime() function to get "+
1550                                           "microsecond-level timing.")
1551                   when :date then (" Used the portable Date.now() method to get millisecond-"+
1552                                    "level timing.")
1553                   else raise end)+
1554                  " Reporting benchmark execution times with 95% confidence "+
1555                  "intervals in milliseconds.",
1556                  columns)
1557   
1558   outp.puts
1559   
1560   def printVMs(outp)
1561     outp.puts createVMsString
1562   end
1563   
1564   def summaryStats(outp, accumulators, name, &proc)
1565     outp.print "   " if $suites.size > 1
1566     outp.print rpad(name, $benchpad)
1567     outp.print " "
1568     accumulators.size.times {
1569       | index |
1570       if index != 0
1571         outp.print " "+accumulators[index].stats(&proc).compareTo(accumulators[index-1].stats(&proc)).shortForm
1572       end
1573       outp.print statsToStr(accumulators[index].stats(&proc))
1574     }
1575     if accumulators.size>=2
1576       outp.print("    "+accumulators[-1].stats(&proc).compareTo(accumulators[0].stats(&proc)).longForm)
1577     end
1578     outp.puts
1579   end
1580   
1581   def meanName(currentMean, preferredMean)
1582     result = "<#{currentMean}>"
1583     if "#{currentMean}Mean" == preferredMean.to_s
1584       result += " *"
1585     end
1586     result
1587   end
1588   
1589   def allSummaryStats(outp, accumulators, preferredMean)
1590     summaryStats(outp, accumulators, meanName("arithmetic", preferredMean)) {
1591       | stat |
1592       stat.arithmeticMean
1593     }
1594     
1595     summaryStats(outp, accumulators, meanName("geometric", preferredMean)) {
1596       | stat |
1597       stat.geometricMean
1598     }
1599     
1600     summaryStats(outp, accumulators, meanName("harmonic", preferredMean)) {
1601       | stat |
1602       stat.harmonicMean
1603     }
1604   end
1605   
1606   $suites.each {
1607     | suite |
1608     printVMs(outp)
1609     if $suites.size > 1
1610       outp.puts "#{suite.name}:"
1611     else
1612       outp.puts
1613     end
1614     suite.benchmarks.each {
1615       | benchmark |
1616       outp.print "   " if $suites.size > 1
1617       outp.print rpad(benchmark.name, $benchpad)
1618       outp.print " "
1619       myConfigs = benchmarksOnVMsForBenchmark[benchmark]
1620       myConfigs.size.times {
1621         | index |
1622         if index != 0
1623           outp.print " "+myConfigs[index].stats.compareTo(myConfigs[index-1].stats).shortForm
1624         end
1625         outp.print statsToStr(myConfigs[index].stats)
1626       }
1627       if $vms.size>=2
1628         outp.print("    "+myConfigs[-1].stats.compareTo(myConfigs[0].stats).to_s)
1629       end
1630       outp.puts
1631     }
1632     outp.puts
1633     allSummaryStats(outp, suitesOnVMsForSuite[suite], suite.preferredMean)
1634     outp.puts if $suites.size > 1
1635   }
1636   
1637   if $suites.size > 1
1638     printVMs(outp)
1639     outp.puts "All benchmarks:"
1640     allSummaryStats(outp, vmStatses, nil)
1641     
1642     outp.puts
1643     printVMs(outp)
1644     outp.puts "Geomean of preferred means:"
1645     outp.print "   "
1646     outp.print rpad("<scaled-result>", $benchpad)
1647     outp.print " "
1648     $vms.size.times {
1649       | index |
1650       if index != 0
1651         outp.print " "+overallResults[index].compareTo(overallResults[index-1]).shortForm
1652       end
1653       outp.print statsToStr(overallResults[index])
1654     }
1655     if overallResults.size>=2
1656       outp.print("    "+overallResults[-1].compareTo(overallResults[0]).longForm)
1657     end
1658     outp.puts
1659   end
1660   outp.puts
1661   
1662   if outp != $stdout
1663     outp.close
1664   end
1665   
1666   if outp != $stdout and not $brief
1667     puts
1668     File.open(reportName) {
1669       | inp |
1670       puts inp.read
1671     }
1672   end
1673   
1674   if $brief
1675     puts(overallResults.collect{|stats| stats.mean}.join("\t"))
1676     puts(overallResults.collect{|stats| stats.confInt}.join("\t"))
1677   end
1678   
1679   
1680 end
1681
1682 begin
1683   $sawBenchOptions = false
1684   
1685   def resetBenchOptionsIfNecessary
1686     unless $sawBenchOptions
1687       $includeSunSpider = false
1688       $includeV8 = false
1689       $includeKraken = false
1690       $sawBenchOptions = true
1691     end
1692   end
1693   
1694   GetoptLong.new(['--rerun', GetoptLong::REQUIRED_ARGUMENT],
1695                  ['--inner', GetoptLong::REQUIRED_ARGUMENT],
1696                  ['--outer', GetoptLong::REQUIRED_ARGUMENT],
1697                  ['--warmup', GetoptLong::REQUIRED_ARGUMENT],
1698                  ['--timing-mode', GetoptLong::REQUIRED_ARGUMENT],
1699                  ['--sunspider-only', GetoptLong::NO_ARGUMENT],
1700                  ['--v8-only', GetoptLong::NO_ARGUMENT],
1701                  ['--kraken-only', GetoptLong::NO_ARGUMENT],
1702                  ['--exclude-sunspider', GetoptLong::NO_ARGUMENT],
1703                  ['--exclude-v8', GetoptLong::NO_ARGUMENT],
1704                  ['--exclude-kraken', GetoptLong::NO_ARGUMENT],
1705                  ['--sunspider', GetoptLong::NO_ARGUMENT],
1706                  ['--v8', GetoptLong::NO_ARGUMENT],
1707                  ['--kraken', GetoptLong::NO_ARGUMENT],
1708                  ['--benchmarks', GetoptLong::REQUIRED_ARGUMENT],
1709                  ['--measure-gc', GetoptLong::OPTIONAL_ARGUMENT],
1710                  ['--force-vm-kind', GetoptLong::REQUIRED_ARGUMENT],
1711                  ['--force-vm-copy', GetoptLong::NO_ARGUMENT],
1712                  ['--dont-copy-vms', GetoptLong::NO_ARGUMENT],
1713                  ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
1714                  ['--brief', GetoptLong::NO_ARGUMENT],
1715                  ['--silent', GetoptLong::NO_ARGUMENT],
1716                  ['--remote', GetoptLong::REQUIRED_ARGUMENT],
1717                  ['--local', GetoptLong::NO_ARGUMENT],
1718                  ['--ssh-options', GetoptLong::REQUIRED_ARGUMENT],
1719                  ['--slave', GetoptLong::NO_ARGUMENT],
1720                  ['--prepare-only', GetoptLong::NO_ARGUMENT],
1721                  ['--analyze', GetoptLong::REQUIRED_ARGUMENT],
1722                  ['--vms', GetoptLong::REQUIRED_ARGUMENT],
1723                  ['--help', '-h', GetoptLong::NO_ARGUMENT]).each {
1724     | opt, arg |
1725     case opt
1726     when '--rerun'
1727       $rerun = intArg(opt,arg,1,nil)
1728     when '--inner'
1729       $inner = intArg(opt,arg,1,nil)
1730     when '--outer'
1731       $outer = intArg(opt,arg,1,nil)
1732     when '--warmup'
1733       $warmup = intArg(opt,arg,0,nil)
1734     when '--timing-mode'
1735       if arg.upcase == "PRECISETIME"
1736         $timeMode = :preciseTime
1737       elsif arg.upcase == "DATE"
1738         $timeMode = :date
1739       elsif arg.upcase == "AUTO"
1740         $timeMode = :auto
1741       else
1742         quickFail("Expected either 'preciseTime', 'date', or 'auto' for --time-mode, but got '#{arg}'.",
1743                   "Invalid argument for command-line option")
1744       end
1745     when '--force-vm-kind'
1746       if arg.upcase == "JSC"
1747         $forceVMKind = :jsc
1748       elsif arg.upcase == "DUMPRENDERTREE"
1749         $forceVMKind = :dumpRenderTree
1750       elsif arg.upcase == "AUTO"
1751         $forceVMKind = nil
1752       else
1753         quickFail("Expected either 'jsc' or 'DumpRenderTree' for --force-vm-kind, but got '#{arg}'.",
1754                   "Invalid argument for command-line option")
1755       end
1756     when '--force-vm-copy'
1757       $needToCopyVMs = true
1758     when '--dont-copy-vms'
1759       $dontCopyVMs = true
1760     when '--sunspider-only'
1761       $includeV8 = false
1762       $includeKraken = false
1763     when '--v8-only'
1764       $includeSunSpider = false
1765       $includeKraken = false
1766     when '--kraken-only'
1767       $includeSunSpider = false
1768       $includeV8 = false
1769     when '--exclude-sunspider'
1770       $includeSunSpider = false
1771     when '--exclude-v8'
1772       $includeV8 = false
1773     when '--exclude-kraken'
1774       $includeKraken = false
1775     when '--sunspider'
1776       resetBenchOptionsIfNecessary
1777       $includeSunSpider = true
1778     when '--v8'
1779       resetBenchOptionsIfNecessary
1780       $includeV8 = true
1781     when '--kraken'
1782       resetBenchOptionsIfNecessary
1783       $includeKraken = true
1784     when '--benchmarks'
1785       $benchmarkPattern = Regexp.new(arg)
1786     when '--measure-gc'
1787       if arg == ''
1788         $measureGC = true
1789       else
1790         $measureGC = arg
1791       end
1792     when '--verbose'
1793       $verbosity += 1
1794     when '--brief'
1795       $brief = true
1796     when '--silent'
1797       $silent = true
1798     when '--remote'
1799       $remoteHosts += arg.split(',')
1800       $needToCopyVMs = true
1801     when '--ssh-options'
1802       $sshOptions << arg
1803     when '--local'
1804       $alsoLocal = true
1805     when '--prepare-only'
1806       $run = false
1807     when '--analyze'
1808       $prepare = false
1809       $run = false
1810       $analyze << arg
1811     when '--help'
1812       usage
1813     else
1814       raise "bad option: #{opt}"
1815     end
1816   }
1817   
1818   # If the --dont-copy-vms option was passed, it overrides the --force-vm-copy option.
1819   if $dontCopyVMs
1820     $needToCopyVMs = false
1821   end
1822   
1823   SUNSPIDER = BenchmarkSuite.new("SunSpider", SUNSPIDER_PATH, :arithmeticMean)
1824   ["3d-cube", "3d-morph", "3d-raytrace", "access-binary-trees",
1825    "access-fannkuch", "access-nbody", "access-nsieve",
1826    "bitops-3bit-bits-in-byte", "bitops-bits-in-byte", "bitops-bitwise-and",
1827    "bitops-nsieve-bits", "controlflow-recursive", "crypto-aes",
1828    "crypto-md5", "crypto-sha1", "date-format-tofte", "date-format-xparb",
1829    "math-cordic", "math-partial-sums", "math-spectral-norm", "regexp-dna",
1830    "string-base64", "string-fasta", "string-tagcloud",
1831    "string-unpack-code", "string-validate-input"].each {
1832     | name |
1833     SUNSPIDER.add SunSpiderBenchmark.new(name)
1834   }
1835
1836   V8 = BenchmarkSuite.new("V8", V8_PATH, :geometricMean)
1837   ["crypto", "deltablue", "earley-boyer", "raytrace",
1838    "regexp", "richards", "splay"].each {
1839     | name |
1840     V8.add V8Benchmark.new(name)
1841   }
1842
1843   KRAKEN = BenchmarkSuite.new("Kraken", KRAKEN_PATH, :arithmeticMean)
1844   ["ai-astar", "audio-beat-detection", "audio-dft", "audio-fft",
1845    "audio-oscillator", "imaging-darkroom", "imaging-desaturate",
1846    "imaging-gaussian-blur", "json-parse-financial",
1847    "json-stringify-tinderbox", "stanford-crypto-aes",
1848    "stanford-crypto-ccm", "stanford-crypto-pbkdf2",
1849    "stanford-crypto-sha256-iterative"].each {
1850     | name |
1851     KRAKEN.add KrakenBenchmark.new(name)
1852   }
1853
1854   ARGV.each {
1855     | vm |
1856     if vm =~ /([a-zA-Z0-9_ ]+):/
1857       name = $1
1858       nameKind = :given
1859       vm = $~.post_match
1860     else
1861       name = "Conf\##{$vms.length+1}"
1862       nameKind = :auto
1863     end
1864     $stderr.puts "#{name}: #{vm}" if $verbosity >= 1
1865     $vms << VM.new(Pathname.new(vm).realpath, name, nameKind, nil)
1866   }
1867   
1868   if $vms.empty?
1869     quickFail("Please specify at least on configuraiton on the command line.",
1870               "Insufficient arguments")
1871   end
1872   
1873   $vms.each {
1874     | vm |
1875     if vm.vmType != :jsc and $timeMode != :date
1876       $timeMode = :date
1877       $stderr.puts "Warning: using Date.now() instead of preciseTime() because #{vm} doesn't support the latter."
1878     end
1879   }
1880   
1881   if FileTest.exist? BENCH_DATA_PATH
1882     cmd = "rm -rf #{BENCH_DATA_PATH}"
1883     $stderr.puts ">> #{cmd}" if $verbosity >= 2
1884     raise unless system cmd
1885   end
1886   
1887   Dir.mkdir BENCH_DATA_PATH
1888   
1889   if $needToCopyVMs
1890     canCopyIntoBenchPath = true
1891     $vms.each {
1892       | vm |
1893       canCopyIntoBenchPath = false unless vm.canCopyIntoBenchPath
1894     }
1895     
1896     if canCopyIntoBenchPath
1897       $vms.each {
1898         | vm |
1899         $stderr.puts "Copying #{vm} into #{BENCH_DATA_PATH}..."
1900         vm.copyIntoBenchPath
1901       }
1902       $stderr.puts "All VMs are in place."
1903     else
1904       $stderr.puts "Warning: don't know how to copy some VMs into #{BENCH_DATA_PATH}, so I won't do it."
1905     end
1906   end
1907   
1908   if $measureGC and $measureGC != true
1909     found = false
1910     $vms.each {
1911       | vm |
1912       if vm.name == $measureGC
1913         found = true
1914       end
1915     }
1916     unless found
1917       $stderr.puts "Warning: --measure-gc option ignored because no VM is named #{$measureGC}"
1918     end
1919   end
1920   
1921   if $outer*$inner == 1
1922     $stderr.puts "Warning: will only collect one sample per benchmark/VM.  Confidence interval calculation will fail."
1923   end
1924   
1925   $stderr.puts "Using timeMode = #{$timeMode}." if $verbosity >= 1
1926   
1927   $suites = []
1928   
1929   if $includeSunSpider and not SUNSPIDER.empty?
1930     $suites << SUNSPIDER
1931   end
1932   
1933   if $includeV8 and not V8.empty?
1934     $suites << V8
1935   end
1936   
1937   if $includeKraken and not KRAKEN.empty?
1938     $suites << KRAKEN
1939   end
1940   
1941   $benchmarks = []
1942   $suites.each {
1943     | suite |
1944     $benchmarks += suite.benchmarks
1945   }
1946   
1947   $runPlans = []
1948   $vms.each {
1949     | vm |
1950     $benchmarks.each {
1951       | benchmark |
1952       $outer.times {
1953         | iteration |
1954         $runPlans << BenchRunPlan.new(benchmark, vm, iteration)
1955       }
1956     }
1957   }
1958   
1959   $runPlans.shuffle!
1960   
1961   $suitepad = $suites.collect {
1962     | suite |
1963     suite.to_s.size
1964   }.max + 1
1965   
1966   $benchpad = ($benchmarks +
1967                ["<arithmetic> *", "<geometric> *", "<harmonic> *"]).collect {
1968     | benchmark |
1969     if benchmark.respond_to? :name
1970       benchmark.name.size
1971     else
1972       benchmark.size
1973     end
1974   }.max + 1
1975
1976   $vmpad = $vms.collect {
1977     | vm |
1978     vm.to_s.size
1979   }.max + 1
1980   
1981   if $prepare
1982     File.open("#{BENCH_DATA_PATH}/runscript", "w") {
1983       | file |
1984       file.puts "echo \"HOSTNAME:\\c\""
1985       file.puts "hostname"
1986       file.puts "echo"
1987       file.puts "echo \"HARDWARE:\\c\""
1988       file.puts "/usr/sbin/sysctl hw.model"
1989       file.puts "echo"
1990       file.puts "set -e"
1991       $script = file
1992       $runPlans.each_with_index {
1993         | plan, idx |
1994         if $verbosity == 0 and not $silent
1995           text1 = lpad(idx.to_s,$runPlans.size.to_s.size)+"/"+$runPlans.size.to_s
1996           text2 = plan.benchmark.to_s+"/"+plan.vm.to_s
1997           file.puts("echo "+("\r#{text1} #{rpad(text2,$suitepad+1+$benchpad+1+$vmpad)}".inspect)[0..-2]+"\\c\" 1>&2")
1998           file.puts("echo "+("\r#{text1} #{text2}".inspect)[0..-2]+"\\c\" 1>&2")
1999         end
2000         plan.emitRunCode
2001       }
2002       if $verbosity == 0 and not $silent
2003         file.puts("echo "+("\r#{$runPlans.size}/#{$runPlans.size} #{' '*($suitepad+1+$benchpad+1+$vmpad)}".inspect)[0..-2]+"\\c\" 1>&2")
2004         file.puts("echo "+("\r#{$runPlans.size}/#{$runPlans.size}".inspect)+" 1>&2")
2005       end
2006     }
2007   end
2008   
2009   if $run
2010     unless $remoteHosts.empty?
2011       $stderr.puts "Packaging benchmarking directory for remote hosts..." if $verbosity==0
2012       Dir.chdir(TEMP_PATH) {
2013         cmd = "tar -czf payload.tar.gz benchdata"
2014         $stderr.puts ">> #{cmd}" if $verbosity>=2
2015         raise unless system(cmd)
2016       }
2017       
2018       def grokHost(host)
2019         if host =~ /:([0-9]+)$/
2020           "-p " + $1 + " " + $~.pre_match.inspect
2021         else
2022           host.inspect
2023         end
2024       end
2025       
2026       def sshRead(host, command)
2027         cmd = "ssh #{$sshOptions.collect{|x| x.inspect}.join(' ')} #{grokHost(host)} #{command.inspect}"
2028         $stderr.puts ">> #{cmd}" if $verbosity>=2
2029         result = ""
2030         IO.popen(cmd, "r") {
2031           | inp |
2032           inp.each_line {
2033             | line |
2034             $stderr.puts "#{host}: #{line}" if $verbosity>=2
2035             result += line
2036           }
2037         }
2038         raise "#{$?}" unless $?.success?
2039         result
2040       end
2041       
2042       def sshWrite(host, command, data)
2043         cmd = "ssh #{$sshOptions.collect{|x| x.inspect}.join(' ')} #{grokHost(host)} #{command.inspect}"
2044         $stderr.puts ">> #{cmd}" if $verbosity>=2
2045         IO.popen(cmd, "w") {
2046           | outp |
2047           outp.write(data)
2048         }
2049         raise "#{$?}" unless $?.success?
2050       end
2051       
2052       $remoteHosts.each {
2053         | host |
2054         $stderr.puts "Sending benchmark payload to #{host}..." if $verbosity==0
2055         
2056         remoteTempPath = JSON::parse(sshRead(host, "cat ~/.bencher"))["tempPath"]
2057         raise unless remoteTempPath
2058         
2059         sshWrite(host, "cd #{remoteTempPath.inspect} && rm -rf benchdata && tar -xz", IO::read("#{TEMP_PATH}/payload.tar.gz"))
2060         
2061         $stderr.puts "Running on #{host}..." if $verbosity==0
2062         
2063         parseAndDisplayResults(sshRead(host, "cd #{(remoteTempPath+'/benchdata').inspect} && sh runscript"))
2064       }
2065     end
2066     
2067     if not $remoteHosts.empty? and $alsoLocal
2068       $stderr.puts "Running locally..."
2069     end
2070     
2071     if $remoteHosts.empty? or $alsoLocal
2072       parseAndDisplayResults(runAndGetResults)
2073     end
2074   end
2075   
2076   $analyze.each_with_index {
2077     | filename, index |
2078     if index >= 1
2079       puts
2080     end
2081     parseAndDisplayResults(IO::read(filename))
2082   }
2083   
2084   if $prepare and not $run and $analyze.empty?
2085     puts wrap("Benchmarking script and data are in #{BENCH_DATA_PATH}. You can run "+
2086               "the benchmarks and get the results by doing:", 78)
2087     puts
2088     puts "cd #{BENCH_DATA_PATH}"
2089     puts "sh runscript > results.txt"
2090     puts
2091     puts wrap("Then you can analyze the results by running bencher with the same arguments "+
2092               "as now, but replacing --prepare-only with --analyze results.txt.", 78)
2093   end
2094 rescue => e
2095   fail(e)
2096 end
2097   
2098