blob: 0111ee875eba571d3c6090ddcf6e2dd2fd576af2 [file] [log] [blame]
David Reissea2cba82009-03-30 21:35:00 +00001#
2# Licensed to the Apache Software Foundation (ASF) under one
3# or more contributor license agreements. See the NOTICE file
4# distributed with this work for additional information
5# regarding copyright ownership. The ASF licenses this file
6# to you under the Apache License, Version 2.0 (the
7# "License"); you may not use this file except in compliance
8# with the License. You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing,
13# software distributed under the License is distributed on an
14# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15# KIND, either express or implied. See the License for the
16# specific language governing permissions and limitations
17# under the License.
18#
19
Kevin Clarkca8a1b32008-06-18 01:17:06 +000020require 'rubygems'
21$:.unshift File.dirname(__FILE__) + '/../lib'
Dmytro Shteflyuk09475942025-11-19 16:23:42 -050022$:.unshift File.dirname(__FILE__) + '/../ext'
Kevin Clarkca8a1b32008-06-18 01:17:06 +000023require 'thrift'
Kevin Clarkca8a1b32008-06-18 01:17:06 +000024require 'stringio'
Kevin Clarkd3cee022008-06-18 01:19:09 +000025
Kevin Clark77b39b32008-06-18 01:20:16 +000026HOST = '127.0.0.1'
Kevin Clarkca8a1b32008-06-18 01:17:06 +000027PORT = 42587
28
Kevin Clarkca8a1b32008-06-18 01:17:06 +000029###############
30## Server
31###############
32
Kevin Clarkd3cee022008-06-18 01:19:09 +000033class Server
34 attr_accessor :serverclass
35 attr_accessor :interpreter
36 attr_accessor :host
37 attr_accessor :port
Dmytro Shteflyuk67bfb292026-01-28 11:23:50 -050038 attr_accessor :protocol_type
Kevin Clarkca8a1b32008-06-18 01:17:06 +000039
Kevin Clarkd3cee022008-06-18 01:19:09 +000040 def initialize(opts)
41 @serverclass = opts.fetch(:class, Thrift::NonblockingServer)
42 @interpreter = opts.fetch(:interpreter, "ruby")
43 @host = opts.fetch(:host, ::HOST)
44 @port = opts.fetch(:port, ::PORT)
Dmytro Shteflyuk67bfb292026-01-28 11:23:50 -050045 @protocol_type = opts.fetch(:protocol_type, 'binary')
Dmytro Shteflyuk09475942025-11-19 16:23:42 -050046 @tls = opts.fetch(:tls, false)
Kevin Clarkca8a1b32008-06-18 01:17:06 +000047 end
48
Kevin Clarkd3cee022008-06-18 01:19:09 +000049 def start
Kevin Clark75532ee2008-06-18 01:19:14 +000050 return if @serverclass == Object
Kevin Clark66038a02008-06-18 01:19:18 +000051 args = (File.basename(@interpreter) == "jruby" ? "-J-server" : "")
Dmytro Shteflyuk67bfb292026-01-28 11:23:50 -050052 @pipe = IO.popen("#{@interpreter} #{args} #{File.dirname(__FILE__)}/server.rb #{"-tls" if @tls} #{@host} #{@port} #{@serverclass.name} #{@protocol_type}", "r+")
Kevin Clark1a95a1d2008-06-18 01:19:28 +000053 Marshal.load(@pipe) # wait until the server has started
Kevin Clarkfdc9c972008-06-18 01:19:46 +000054 sleep 0.4 # give the server time to actually start spawning sockets
Kevin Clarkca8a1b32008-06-18 01:17:06 +000055 end
56
Kevin Clarkd3cee022008-06-18 01:19:09 +000057 def shutdown
58 return unless @pipe
59 Marshal.dump(:shutdown, @pipe)
60 begin
61 @pipe.read(10) # block until the server shuts down
62 rescue EOFError
Kevin Clarkca8a1b32008-06-18 01:17:06 +000063 end
Kevin Clarkd3cee022008-06-18 01:19:09 +000064 @pipe.close
65 @pipe = nil
Kevin Clarkca8a1b32008-06-18 01:17:06 +000066 end
67end
68
Kevin Clarkca8a1b32008-06-18 01:17:06 +000069class BenchmarkManager
Kevin Clarkd3cee022008-06-18 01:19:09 +000070 def initialize(opts, server)
Kevin Clark2ddd8ed2008-06-18 01:18:35 +000071 @socket = opts.fetch(:socket) do
72 @host = opts.fetch(:host, 'localhost')
73 @port = opts.fetch(:port)
74 nil
75 end
Kevin Clarkca8a1b32008-06-18 01:17:06 +000076 @num_processes = opts.fetch(:num_processes, 40)
77 @clients_per_process = opts.fetch(:clients_per_process, 10)
78 @calls_per_client = opts.fetch(:calls_per_client, 50)
Kevin Clarkd3cee022008-06-18 01:19:09 +000079 @interpreter = opts.fetch(:interpreter, "ruby")
80 @server = server
Kevin Clarkfdc9c972008-06-18 01:19:46 +000081 @log_exceptions = opts.fetch(:log_exceptions, false)
Dmytro Shteflyuk67bfb292026-01-28 11:23:50 -050082 @protocol_type = opts.fetch(:protocol_type, 'binary')
Dmytro Shteflyuk09475942025-11-19 16:23:42 -050083 @tls = opts.fetch(:tls, false)
Kevin Clarkca8a1b32008-06-18 01:17:06 +000084 end
85
86 def run
87 @pool = []
88 @benchmark_start = Time.now
89 puts "Spawning benchmark processes..."
90 @num_processes.times do
91 spawn
Kevin Clarkfdc9c972008-06-18 01:19:46 +000092 sleep 0.02 # space out spawns
Kevin Clarkca8a1b32008-06-18 01:17:06 +000093 end
94 collect_output
95 @benchmark_end = Time.now # we know the procs are done here
96 translate_output
97 analyze_output
98 report_output
99 end
100
101 def spawn
Dmytro Shteflyuk67bfb292026-01-28 11:23:50 -0500102 pipe = IO.popen("#{@interpreter} #{File.dirname(__FILE__)}/client.rb #{"-log-exceptions" if @log_exceptions} #{"-tls" if @tls} #{@host} #{@port} #{@clients_per_process} #{@calls_per_client} #{@protocol_type}")
Kevin Clarkd3cee022008-06-18 01:19:09 +0000103 @pool << pipe
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000104 end
105
Kevin Clark2ddd8ed2008-06-18 01:18:35 +0000106 def socket_class
107 if @socket
108 Thrift::UNIXSocket
Dmytro Shteflyuk09475942025-11-19 16:23:42 -0500109 elsif @tls
110 Thrift::SSLSocket
Kevin Clark2ddd8ed2008-06-18 01:18:35 +0000111 else
112 Thrift::Socket
113 end
114 end
115
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000116 def collect_output
117 puts "Collecting output..."
118 # read from @pool until all sockets are closed
119 @buffers = Hash.new { |h,k| h[k] = '' }
120 until @pool.empty?
121 rd, = select(@pool)
122 next if rd.nil?
123 rd.each do |fd|
124 begin
Kevin Clarkfb5c0eb2008-06-18 01:19:04 +0000125 @buffers[fd] << fd.readpartial(4096)
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000126 rescue EOFError
127 @pool.delete fd
128 end
129 end
130 end
131 end
132
133 def translate_output
134 puts "Translating output..."
135 @output = []
136 @buffers.each do |fd, buffer|
137 strio = StringIO.new(buffer)
138 logs = []
139 begin
140 loop do
141 logs << Marshal.load(strio)
142 end
143 rescue EOFError
144 @output << logs
145 end
146 end
147 end
148
149 def analyze_output
150 puts "Analyzing output..."
151 call_times = []
152 client_times = []
153 connection_failures = []
Kevin Clarkd2719792008-06-18 01:19:37 +0000154 connection_errors = []
Kevin Clark66038a02008-06-18 01:19:18 +0000155 shortest_call = 0
156 shortest_client = 0
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000157 longest_call = 0
158 longest_client = 0
159 @output.each do |logs|
160 cur_call, cur_client = nil
161 logs.each do |tok, time|
162 case tok
163 when :start
164 cur_client = time
165 when :call_start
166 cur_call = time
167 when :call_end
168 delta = time - cur_call
169 call_times << delta
170 longest_call = delta unless longest_call > delta
Kevin Clark66038a02008-06-18 01:19:18 +0000171 shortest_call = delta if shortest_call == 0 or delta < shortest_call
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000172 cur_call = nil
173 when :end
174 delta = time - cur_client
175 client_times << delta
176 longest_client = delta unless longest_client > delta
Kevin Clark66038a02008-06-18 01:19:18 +0000177 shortest_client = delta if shortest_client == 0 or delta < shortest_client
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000178 cur_client = nil
179 when :connection_failure
180 connection_failures << time
Kevin Clarkd2719792008-06-18 01:19:37 +0000181 when :connection_error
182 connection_errors << time
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000183 end
184 end
185 end
186 @report = {}
187 @report[:total_calls] = call_times.inject(0.0) { |a,t| a += t }
188 @report[:avg_calls] = @report[:total_calls] / call_times.size
189 @report[:total_clients] = client_times.inject(0.0) { |a,t| a += t }
190 @report[:avg_clients] = @report[:total_clients] / client_times.size
191 @report[:connection_failures] = connection_failures.size
Kevin Clarkd2719792008-06-18 01:19:37 +0000192 @report[:connection_errors] = connection_errors.size
Kevin Clark66038a02008-06-18 01:19:18 +0000193 @report[:shortest_call] = shortest_call
194 @report[:shortest_client] = shortest_client
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000195 @report[:longest_call] = longest_call
196 @report[:longest_client] = longest_client
197 @report[:total_benchmark_time] = @benchmark_end - @benchmark_start
198 @report[:fastthread] = $".include?('fastthread.bundle')
199 end
200
201 def report_output
202 fmt = "%.4f seconds"
203 puts
204 tabulate "%d",
Kevin Clark75532ee2008-06-18 01:19:14 +0000205 [["Server class", "%s"], @server.serverclass == Object ? "" : @server.serverclass],
Kevin Clarkd3cee022008-06-18 01:19:09 +0000206 [["Server interpreter", "%s"], @server.interpreter],
207 [["Client interpreter", "%s"], @interpreter],
Dmytro Shteflyuk67bfb292026-01-28 11:23:50 -0500208 [["Protocol type", "%s"], @protocol_type],
Kevin Clark2ddd8ed2008-06-18 01:18:35 +0000209 [["Socket class", "%s"], socket_class],
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000210 ["Number of processes", @num_processes],
211 ["Clients per process", @clients_per_process],
212 ["Calls per client", @calls_per_client],
213 [["Using fastthread", "%s"], @report[:fastthread] ? "yes" : "no"]
214 puts
Kevin Clark75532ee2008-06-18 01:19:14 +0000215 failures = (@report[:connection_failures] > 0)
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000216 tabulate fmt,
Kevin Clarkd2719792008-06-18 01:19:37 +0000217 [["Connection failures", "%d", [:red, :bold]], @report[:connection_failures]],
218 [["Connection errors", "%d", [:red, :bold]], @report[:connection_errors]],
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000219 ["Average time per call", @report[:avg_calls]],
220 ["Average time per client (%d calls)" % @calls_per_client, @report[:avg_clients]],
221 ["Total time for all calls", @report[:total_calls]],
222 ["Real time for benchmarking", @report[:total_benchmark_time]],
Kevin Clark66038a02008-06-18 01:19:18 +0000223 ["Shortest call time", @report[:shortest_call]],
224 ["Longest call time", @report[:longest_call]],
225 ["Shortest client time (%d calls)" % @calls_per_client, @report[:shortest_client]],
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000226 ["Longest client time (%d calls)" % @calls_per_client, @report[:longest_client]]
227 end
228
Kevin Clark75532ee2008-06-18 01:19:14 +0000229 ANSI = {
230 :reset => 0,
231 :bold => 1,
232 :black => 30,
233 :red => 31,
234 :green => 32,
235 :yellow => 33,
236 :blue => 34,
237 :magenta => 35,
238 :cyan => 36,
239 :white => 37
240 }
241
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000242 def tabulate(fmt, *labels_and_values)
Bryan Duxbury74c3de62009-03-20 16:54:33 +0000243 labels = labels_and_values.map { |l| Array === l ? l.first : l }
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000244 label_width = labels.inject(0) { |w,l| l.size > w ? l.size : w }
245 labels_and_values.each do |(l,v)|
246 f = fmt
Kevin Clark75532ee2008-06-18 01:19:14 +0000247 l, f, c = l if Array === l
248 fmtstr = "%-#{label_width+1}s #{f}"
Kevin Clarkd2719792008-06-18 01:19:37 +0000249 if STDOUT.tty? and c and v.to_i > 0
Kevin Clark75532ee2008-06-18 01:19:14 +0000250 fmtstr = "\e[#{[*c].map { |x| ANSI[x] } * ";"}m" + fmtstr + "\e[#{ANSI[:reset]}m"
251 end
252 puts fmtstr % [l+":", v]
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000253 end
254 end
255end
256
257def resolve_const(const)
258 const and const.split('::').inject(Object) { |k,c| k.const_get(c) }
259end
260
261puts "Starting server..."
Dmytro Shteflyuk67bfb292026-01-28 11:23:50 -0500262protocol_type = ENV['THRIFT_PROTOCOL'] || 'binary'
Kevin Clarkd3cee022008-06-18 01:19:09 +0000263args = {}
264args[:interpreter] = ENV['THRIFT_SERVER_INTERPRETER'] || ENV['THRIFT_INTERPRETER'] || "ruby"
265args[:class] = resolve_const(ENV['THRIFT_SERVER']) || Thrift::NonblockingServer
Kevin Clarkd8d0d602008-06-18 01:20:10 +0000266args[:host] = ENV['THRIFT_HOST'] || HOST
267args[:port] = (ENV['THRIFT_PORT'] || PORT).to_i
Dmytro Shteflyuk09475942025-11-19 16:23:42 -0500268args[:tls] = ENV['THRIFT_TLS'] == 'true'
Dmytro Shteflyuk67bfb292026-01-28 11:23:50 -0500269args[:protocol_type] = protocol_type
Kevin Clarkd3cee022008-06-18 01:19:09 +0000270server = Server.new(args)
271server.start
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000272
Kevin Clarkd8d0d602008-06-18 01:20:10 +0000273args = {}
274args[:host] = ENV['THRIFT_HOST'] || HOST
275args[:port] = (ENV['THRIFT_PORT'] || PORT).to_i
Dmytro Shteflyuk09475942025-11-19 16:23:42 -0500276args[:tls] = ENV['THRIFT_TLS'] == 'true'
Kevin Clark4b429ad2008-06-18 01:20:06 +0000277args[:num_processes] = (ENV['THRIFT_NUM_PROCESSES'] || 40).to_i
Kevin Clarkfdc9c972008-06-18 01:19:46 +0000278args[:clients_per_process] = (ENV['THRIFT_NUM_CLIENTS'] || 5).to_i
279args[:calls_per_client] = (ENV['THRIFT_NUM_CALLS'] || 50).to_i
Kevin Clarkd3cee022008-06-18 01:19:09 +0000280args[:interpreter] = ENV['THRIFT_CLIENT_INTERPRETER'] || ENV['THRIFT_INTERPRETER'] || "ruby"
Kevin Clarkfdc9c972008-06-18 01:19:46 +0000281args[:log_exceptions] = !!ENV['THRIFT_LOG_EXCEPTIONS']
Dmytro Shteflyuk67bfb292026-01-28 11:23:50 -0500282args[:protocol_type] = protocol_type
Kevin Clarkd3cee022008-06-18 01:19:09 +0000283BenchmarkManager.new(args, server).run
Kevin Clarkca8a1b32008-06-18 01:17:06 +0000284
Kevin Clarkd3cee022008-06-18 01:19:09 +0000285server.shutdown