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
26require 'rubygems'
27
28require 'getoptlong'
29require 'pathname'
30require 'tempfile'
31require 'socket'
32
33begin
34  require 'json'
35rescue 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
38end
39
40# Configuration
41
42CONFIGURATION_FLNM = ENV["HOME"]+"/.bencher"
43
44unless 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
56end
57
58CONFIGURATION = JSON.parse(File::read(CONFIGURATION_FLNM))
59
60SUNSPIDER_PATH = CONFIGURATION["sunSpiderPath"]
61V8_PATH = CONFIGURATION["v8Path"]
62KRAKEN_PATH = CONFIGURATION["krakenPath"]
63TEMP_PATH = CONFIGURATION["tempPath"]
64BENCH_DATA_PATH = TEMP_PATH + "/benchdata"
65
66IBR_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
237def smallUsage
238  puts "Use the --help option to get basic usage information."
239  exit 1
240end
241
242def 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
317end
318
319def 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
328end
329
330def quickFail(r1,r2)
331  $stderr.puts "#{$0}: #{r1}"
332  puts
333  fail(r2)
334end
335
336def 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
351end
352
353def computeMean(array)
354  sum=0.0
355  array.each {
356    | value |
357    sum += value
358  }
359  sum/array.length
360end
361
362def computeGeometricMean(array)
363  mult=1.0
364  array.each {
365    | value |
366    mult*=value
367  }
368  mult**(1.0/array.length)
369end
370
371def computeHarmonicMean(array)
372  1.0 / computeMean(array.collect{ | value | 1.0 / value })
373end
374
375def 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
394end
395
396class Array
397  def shuffle!
398    size.downto(1) { |n| push delete_at(rand(n)) }
399    self
400  end
401end
402
403def inverseBetaRegularized(n)
404  IBR_LOOKUP[n-1]
405end
406
407def numToStr(num)
408  "%.4f"%(num.to_f)
409end
410
411class 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
433end
434
435class 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
453end
454
455class 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
473end
474
475class 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
497end
498
499class 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
599end
600
601def doublePuts(out1,out2,msg)
602  out1.puts "#{out2.path}: #{msg}" if $verbosity>=3
603  out2.puts msg
604end
605
606class 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
634end
635
636$dataFiles={}
637def 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]
645end
646
647def 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
767end
768
769def emitBenchRunCode(name, plan, benchDataPath, benchPath)
770  plan.vm.emitRunCode(emitBenchRunCodeFile(name, plan, benchDataPath, benchPath))
771end
772
773def 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]
780end
781
782class 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
823end
824
825class 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
959end
960
961class 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
995end
996
997module 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
1008end
1009
1010class 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
1020end
1021
1022class 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
1032end
1033
1034class 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
1044end
1045
1046class 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
1101end
1102
1103class 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
1129end
1130
1131class 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
1171end
1172
1173class 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
1201end
1202
1203class 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
1239end
1240
1241def lpad(str,chars)
1242  if str.length>chars
1243    str
1244  else
1245    "%#{chars}s"%(str)
1246  end
1247end
1248
1249def rpad(str,chars)
1250  while str.length<chars
1251    str+=" "
1252  end
1253  str
1254end
1255
1256def center(str,chars)
1257  while str.length<chars
1258    str+=" "
1259    if str.length<chars
1260      str=" "+str
1261    end
1262  end
1263  str
1264end
1265
1266def 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
1276end
1277
1278def plural(num)
1279  if num == 1
1280    ""
1281  else
1282    "s"
1283  end
1284end
1285
1286def 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"
1300end
1301
1302def 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
1313end
1314
1315def 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
1680end
1681
1682begin
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
2094rescue => e
2095  fail(e)
2096end
2097
2098
2099