roadrunner 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/db.rb ADDED
@@ -0,0 +1,51 @@
1
+ #
2
+
3
+ module RoadRunnerModule
4
+
5
+ module Dbhelper
6
+
7
+ def migrate(tables)
8
+ tables.each do |table|
9
+ case table
10
+ when "scenarios" then
11
+ unless ActiveRecord::Base.connection.tables.include?(table.to_s) then
12
+ ActiveRecord::Base.connection.create_table(table.to_sym) do |t|
13
+ t.column :name, :string, :limit => 256
14
+ t.column :create_at,:string, :limit => 32
15
+ t.column :script, :string,:limit => 10240
16
+ t.column :author, :string, :default => 'Anonymous'
17
+ t.column :author, :tps, :int
18
+ t.column :desc, :string
19
+ end
20
+ end
21
+
22
+ when "transactions" then
23
+ unless ActiveRecord::Base.connection.tables.include?(table.to_s) then
24
+ ActiveRecord::Base.connection.create_table(table.to_sym) do |t|
25
+ t.column :name,:string,:limit => 256
26
+ t.column :scenario_id,:string,:limit => 256
27
+ t.column :success_rate,:string, :limit => 8
28
+ t.column :create_at,:string, :limit => 32
29
+
30
+ end
31
+ end
32
+
33
+ when "records" then
34
+ unless ActiveRecord::Base.connection.tables.include?(table.to_s) then
35
+ ActiveRecord::Base.connection.create_table(table.to_sym) do |t|
36
+ t.column :cost, :string, :limit => 32
37
+ t.column :ts, :string, :limit => 32
38
+ t.column :seq, :int
39
+ t.column :stats,:int
40
+ t.column :transaction_id,:string,:limit => 256
41
+ t.column :create_at,:string, :limit => 32
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ module_function :migrate
49
+ end
50
+
51
+ end
data/lib/ended.rb ADDED
@@ -0,0 +1,14 @@
1
+ #Here is LR End function
2
+
3
+ module RoadRunnerModule
4
+ def ended(&blk)
5
+
6
+ if block_given?
7
+ @endBlk=blk
8
+ register_transactions('end',&blk)
9
+ else
10
+ raise ArgumentError, "no block"
11
+ end
12
+
13
+ end
14
+ end
data/lib/init.rb ADDED
@@ -0,0 +1,14 @@
1
+ #Here is LR init function
2
+
3
+ module RoadRunnerModule
4
+ def init(&blk)
5
+
6
+ if block_given?
7
+ @initBlk=blk
8
+ register_transactions('init',&blk)
9
+ else
10
+ raise ArgumentError, "no block"
11
+ end
12
+
13
+ end
14
+ end
data/lib/model.rb ADDED
@@ -0,0 +1,10 @@
1
+ class Scenario < ActiveRecord::Base
2
+ has_many :transactions
3
+ end
4
+ class Transaction < ActiveRecord::Base
5
+ belongs_to :scenario
6
+ has_many :records
7
+ end
8
+ class Record < ActiveRecord::Base
9
+ belongs_to :transaction
10
+ end
data/lib/report.rb ADDED
@@ -0,0 +1,100 @@
1
+ #After roadRunner.run() executed,
2
+ #execute roadRunner.report() show the Performance report
3
+
4
+ module RoadRunnerModule
5
+
6
+ def report(label='',width=0)
7
+ p " Wait for moment,the report is collecting......"
8
+ content = if @rep
9
+ @succRates = getSuccessRate @transactions
10
+ <<-DOC
11
+ #{"Performance Reports".center(50, '*')}
12
+ #{label.ljust(width)}
13
+ #{Benchmark::Tms::CAPTION}
14
+ #{@rep.format}
15
+ #{'--'*32}
16
+ The Virtual User is #{@users}.
17
+ Total Execute #{@iterations*@users} Action(s).
18
+ Total Cost #{@rep.real} Second(s).
19
+ This Scenario's TPS : #{@tps}.
20
+ The longest action cost #{@longest || 0} seconds.
21
+ #{'Transaction Report'.center(50, '-')}
22
+ #{@transactions.inject("") { |str,k|
23
+ str += "#{k[0].to_s} : count => #{k[1].size} time(s) , cost => #{k[1].inject(0){|c,v|c+=v[:cost].to_f}.to_s[0..3]} sec(s) , success rate => #{@succRates[k[0]]}\n "
24
+ }.gsub(/\n$/,'')}
25
+ #{'--'*32}
26
+ User defined params as below:
27
+ #{@global}
28
+ #{"End of Reports".center(50, '*')}
29
+ DOC
30
+ else
31
+ "None Report before RoadRunner run."
32
+ end
33
+ puts content
34
+ self.log.info content
35
+ end
36
+
37
+ def save_report(opts={:author=>'Anonymous',:script=>""})
38
+
39
+ p " Saving report to database......"
40
+ opts={:author=>'Anonymous',:script=>""}.merge opts
41
+ if @data_write then
42
+ unless opts[:name] then
43
+ self.log.warn "scenario name may be needed."
44
+ require 'uuidtools' unless defined?UUID
45
+ # scenario || UUID.random_create.to_s is for debug
46
+ opts[:name] = opts[:name] || UUID.random_create.to_s
47
+ self.log.info "scenario is created as #{opts[:name]}"
48
+ end
49
+ # => scenario = Scenario.find_or_create_by_name(opts.merge({:create_at=>Time.now})).shift
50
+ scenario = Scenario.find_or_create_by_name(opts.merge({:create_at=>Time.now}))
51
+ scenario.tps+=@iterations*@users/@rep.real
52
+
53
+ # => if opts[:script] is set as <your script>.rb 's path
54
+ # => opts[:script] = __FILE__
55
+ # => then scenario.script will be set.
56
+ if FileTest.exist?(opts[:script]) then
57
+ scenario.script = ""
58
+ IO.foreach(opts[:script]){|x|scenario.script << x}
59
+ end
60
+
61
+ @succRates = @succRates || (getSuccessRate @transactions)
62
+ # k is transaction name ,
63
+ # v is reports in every transaction.
64
+ @transactions.each do |k,v|
65
+ transaction = Transaction.new({:name=>k,:create_at=>Time.now,:success_rate=>@succRates[k]})
66
+ v.each_index do |id|
67
+ transaction.records << Record.new(v[id].merge({:seq=>id,:ts=>v[id][:create_at].to_f-@strat_time.to_f}))
68
+ self.log.debug "v[#{id}] = #{v[id].inspect}"
69
+ end
70
+ scenario.transactions << transaction
71
+ end
72
+ begin
73
+ scenario.save!
74
+ p " Saved OK!"
75
+ rescue =>e
76
+ self.log.error e
77
+ p e
78
+ end
79
+ self.log.info "records has saved in DB."
80
+ else
81
+ p ' Error:You didn\'t connect any database.'
82
+ self.log.error 'You didn\'t connect any database.'
83
+ end
84
+ end
85
+
86
+ def getSuccessRate(transactions)
87
+ result = {}
88
+ transactions.each do |h|
89
+ success,faile = 0,0
90
+ h[1].each do |r|
91
+ r[:stats]==0?success+=1:faile+=1
92
+ end
93
+ result[h[0]]=success.to_f/(success+faile)
94
+ end
95
+ result
96
+ end
97
+
98
+ private :getSuccessRate
99
+
100
+ end
data/lib/roadrunner.rb ADDED
@@ -0,0 +1,122 @@
1
+ # Author : zheng.cuizh@gmail.com
2
+ # RoadRunner named lik LoadRunner which is a industry performance test tool of HP.
3
+ # I'm happy to user LR and Ruby,
4
+ # so i coded the RoadRunner.
5
+ # Using RR,you'll find somethings similar to LR.
6
+
7
+ $:.unshift File.dirname(__FILE__)
8
+ require 'rubygems'
9
+ require 'benchmark'
10
+ require 'logger'
11
+ require 'pp'
12
+
13
+ Dir[File.join(File.dirname(__FILE__),"*.rb")].select{|x|not (x =~ /.*model\.rb/ || x =~ /.*db\.rb/ || x =~ /.*roadrunner\.rb/)}.each { |x| require x }
14
+
15
+ class RoadRunner
16
+
17
+ include RoadRunnerModule
18
+ attr :iterations, true
19
+ attr :users,true
20
+ attr :global,true
21
+ attr :iterationId
22
+ attr :record
23
+ attr :userId
24
+ attr :mode,true
25
+ attr :log
26
+ attr :tps
27
+ alias_method :g,:global
28
+ alias_method :g=,:global=
29
+
30
+ def initialize(opts={})
31
+ # => DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
32
+ opts = {:out=>File.join(File.dirname(__FILE__),'..','log',"stdout.log"),:frequency=>'daily',:size=>1048576,:level=>Logger::DEBUG}.merge opts
33
+ @users,@iterations,@longest,@userId,@iterationId=1,1,0,0,0
34
+ @initBlk,@actBlk,@endBlk=proc{},proc{},proc{}
35
+ @global,@transactions={},{}
36
+ @log=Logger.new(opts[:out],opts[:frequency],opts[:size])
37
+ @log.level=opts[:level]
38
+ # => mode : sequence<default>,thread|t,process|p
39
+ @mode='sequence'
40
+
41
+ @transaction_blk={}
42
+
43
+ if block_given? then
44
+ begin
45
+ gem 'active_record'
46
+ require 'active_record'
47
+ rescue Exception => e
48
+ require 'activerecord'
49
+ end
50
+
51
+ require 'db'
52
+ require "model"
53
+
54
+ session = {}
55
+ _session = yield session
56
+ session = _session if session == {}
57
+
58
+ begin
59
+ ActiveRecord::Base.establish_connection(
60
+ #
61
+ # === session defined in block :
62
+ #
63
+ # :adapter=>session[:adapter],
64
+ # :encoding=>session[:encoding],
65
+ # :database=>session[:database],
66
+ # :username=>session[:username],
67
+ # :password=>session[:password],
68
+ # :host=>session[:host]
69
+ #
70
+ session
71
+ )
72
+ self.log.info "connect db ok."
73
+ rescue =>ex
74
+ self.log.error "adapter:#{session[:adapter]}.connect db faile."
75
+ end
76
+
77
+ default_report_table = %w"scenarios transactions records"
78
+
79
+ unless @data_write = default_report_table.inject(true){|r,t| r = r && ActiveRecord::Base.connection.tables.include?(t)} then
80
+ self.log.warn "table rreports doesn't defined and will be created."
81
+ Dbhelper::migrate default_report_table
82
+ if @data_write = default_report_table.inject(true){|r,t| r = r && ActiveRecord::Base.connection.tables.include?(t)} then
83
+ self.log.info "create table #{default_report_table.inspect} successful."
84
+ else
85
+ self.log.error "create table #{default_report_table.inspect} fail."
86
+ end
87
+ end
88
+ require 'model' unless defined? Scenario
89
+ self.log.debug 'model Rreport is reqruired.'
90
+ end
91
+ end
92
+
93
+ def transaction(name,&blk)
94
+ @transactions[name] || @transactions[name] = []
95
+ status = "AUTO"
96
+ # => Status is the last expression in the action block
97
+ rcost = Benchmark::realtime{status = yield} if block_given?
98
+ # => status's value is:
99
+ # => 0=>success
100
+ # => -1=>faile
101
+ case status
102
+ when false,'false','-1',-1 then status = -1
103
+ else status = 0
104
+ end
105
+ # {:stats=>status,:cost=>rcost,:create_at=>Time.now} is one record
106
+ @transactions[name] << {:stats=>status,:cost=>rcost,:create_at=>Time.now}
107
+ # => the below sentence cost a lot of system resource
108
+ # => if you run for production result,keep it annotated!!!
109
+ # self.log.debug @transactions[name].inspect
110
+ rcost
111
+ end
112
+
113
+ def register_transactions(name,&blk)
114
+ @transaction_blk[name]=blk
115
+ end
116
+
117
+ def method_missing(name,*args,&blk)
118
+ # self.transaction(name.to_s,&blk)
119
+ register_transactions(name,&blk)
120
+ end
121
+
122
+ end
data/lib/rrhelper.rb ADDED
@@ -0,0 +1,25 @@
1
+ # Rrhelper will include many helpful methods for RoadRunner
2
+
3
+ module RoadRunnerModule
4
+
5
+ def iterationId
6
+ if(@mode=='thread')then
7
+ @thread_pool[Thread.current][:iterationId]
8
+ else
9
+ @iterationId
10
+ end
11
+ end
12
+
13
+ def userId
14
+ if(@mode=='thread')then
15
+ @thread_pool[Thread.current][:userId]
16
+ else
17
+ @userId
18
+ end
19
+ end
20
+
21
+ module Rrhelper
22
+
23
+ end
24
+
25
+ end
data/lib/rrmonitor.rb ADDED
@@ -0,0 +1,174 @@
1
+ require "rubygems"
2
+ require "net/ssh"
3
+ require 'fileutils'
4
+
5
+ module RoadRunnerModule
6
+
7
+ =begin
8
+ RRMonitor is used for Xnix system.
9
+ =end
10
+
11
+ class RRMonitor
12
+ def initialize(opt={:server=>"0.0.0.0", :username=>"admin", :password=>"123456"})
13
+ raise(ArgumentError,"Logger is needed!") unless opt[:log]
14
+ @log=opt[:log]
15
+
16
+ @server=opt[:server]
17
+
18
+ begin
19
+ @sess=Net::SSH.start(opt[:server], opt[:username], :password => opt[:password],:timeout => 120)
20
+ @log.info "#{opt[:server]} ssh connection OK."
21
+ rescue Exception => e
22
+ @log.error e
23
+ @log.error opt.inspect
24
+ raise e
25
+ end
26
+
27
+
28
+ @log.info("monitor initialize.".center(60,"*"))
29
+ @log.debug("monitor => #{self.inspect}")
30
+
31
+ @ts=( opt[:ts] || Time.now.to_i )
32
+
33
+ @path="#{File.join File.dirname(__FILE__),'..','log'}/monitor/#{@ts}"
34
+ @log.info "monitor local log path :#{@path}"
35
+
36
+ @rpath="~/.monit/#{@ts}"
37
+ @log.info "monitor remote log path(rpath) :#{@rpath}"
38
+
39
+ @sess.exec!("rm -rf #{@rpath}")
40
+ @sess.exec!("mkdir -p #{@rpath}")
41
+
42
+ @pids={}
43
+ end
44
+
45
+ =begin
46
+ run!(cmd) =>
47
+ the cmd will be execed at remote server(0),
48
+ and the stdout will write in log file.
49
+
50
+ cmd =>[
51
+ 'ifstat',# network io
52
+ 'iostat 3',# disk io
53
+ 'vmstat 3',# memory info
54
+ 'while((1>0));do sar -u -d 3 10; done',# disk and cpu info
55
+ 'while((1>0));do sar -u -n DEV 3 10; done'# network and cpu info
56
+ ]
57
+
58
+ "while((1>0));do #{cmd}; done" is needed for non-consist output cmd.
59
+
60
+ cmd sample =>
61
+
62
+ run!(["while((1>0));do /home/admin/apsara/build/debug64/bin/ku --command=getallpart --appname=blog1 --interactive=false|tail -n1; done"])
63
+
64
+ when exec!(cmd)
65
+ =>
66
+ exec!("while((1>0));do /home/admin/apsara/build/debug64/bin/ku --command=getallpart --appname=blog1 --interactive=false|tail -n1; done >> #{kupath} 2>/dev/null &")
67
+ =end
68
+
69
+ def run!(cmd=['ifstat','iostat 3','while((1>0));do sar -u -d 3 10; done','vmstat 3','while((1>0));do sar -u -n DEV 3 10; done'])
70
+ (@log.error "run!(cmd) cmd type require array.";exit) unless cmd.is_a?(Array)
71
+ (@log.error "run!(cmd) require commands.";exit) if cmd.none?
72
+
73
+ @cmd=cmd
74
+
75
+ begin
76
+ @log.info("RUN!".center(60,"*"))
77
+ @log.debug @server
78
+ cmd.each { |e| self.exec!(e)}
79
+ rescue Exception => e
80
+ @log.error("#{self.inspect} run error!")
81
+ @log.error(e.to_s)
82
+ # raise e
83
+ end
84
+
85
+ end
86
+
87
+ def exec!(cmd)
88
+ # >> cmd='while((1>0));do sar -u -d 3 10; # done';cmdpath="#{cmd.gsub(/([^a-zA-Z0-9\-])/,'_')}.log"
89
+ # => "while__1_0___do_sar_-u_-d_3_10__done.log"
90
+
91
+ # cmdpath="#{@rpath}/#{cmd.gsub(/[\s|\t|\$|\`|\(|\)|\&|\>|\<|\;|\||\'|\"]/,'_')}.log"
92
+ cmdpath="#{@rpath}/#{cmd.gsub(/[^a-zA-Z0-9\-]/,'_')}.log"
93
+ @cmdpath=cmdpath
94
+
95
+ @log.debug "#{cmd} path => #{cmdpath}"
96
+ @log.info "Start #{cmd}.".center(60,"*")
97
+
98
+ @log.debug("#{cmd} >> #{cmdpath} 2>/dev/null &")
99
+ @sess.exec!("#{cmd} >> #{cmdpath} 2>/dev/null &")
100
+
101
+ @pids[cmd]=@sess.exec!("echo $!")
102
+ @log.debug("#{cmd} pid => #{@pids[:ku]}")
103
+ end
104
+
105
+ def stop!
106
+ @pids.each do |key,value|
107
+ @sess.exec!("kill -9 #{value}")
108
+ @log.info("#{key} => #{value} stop!")
109
+ end
110
+
111
+ # if other user exec the same cmd,
112
+ # below cmd will kill all of the cmd.
113
+
114
+ # @sess.exec!(%q@ps axf|grep while|grep -v grep|awk '{printf "%s\n",$1}'|xargs kill -9@)
115
+ #
116
+ # @cmd.each { |e|
117
+ # @sess.exec!(%q@ps axf|grep "#{e}"|grep -v grep|awk '{printf "%s\n",$1}'|xargs kill -9@)
118
+ # }
119
+
120
+ @log.debug @server
121
+ @log.info("Monitor STOP!".center(60,"*"))
122
+ end
123
+
124
+ def collect
125
+ @log.debug "mkdir -p #{@path}/#{@server}"
126
+ %x{mkdir -p #{@path}/#{@server}}
127
+
128
+ path = @path.gsub('~',`echo ~`.gsub(/[\r\n]/,''))
129
+ @log.info "Collecting...".center(60,"*")
130
+ @log.debug "collect path => #{path}/#{@server}"
131
+ `scp -r admin@#{@server}:#{@rpath} #{path}/#{@server}`
132
+ @log.info "collected files => #{Dir[path+'/'+@server+'/**/*'].join($/)}"
133
+ end
134
+
135
+ #-----------------------------monit method--------------------------------#
136
+
137
+ def self.monit(servers,log,cmd=['ifstat','iostat 3','while((1>0));do sar -u -d 3 10; done','vmstat 3','while((1>0));do sar -u -n DEV 3 10; done'],ts="PerformanceTest")
138
+
139
+ case servers
140
+ when Hash
141
+ # do nothing
142
+ when String
143
+ File.open( servers ) { |yf|
144
+ begin
145
+ servers=YAML::load( yf )
146
+ rescue Exception => e
147
+ log.error("Servers' config YAML Load Error!Plz check your yaml file => #{servers}.#{$/}#{e.to_s}")
148
+ exit 1
149
+ end
150
+ }
151
+ end
152
+
153
+ _servers = {}
154
+
155
+ servers.each do |ip,obj|
156
+ _servers[ip] = RoadRunnerModule::RRMonitor.new({:server=>ip, :log=>log, :username=>obj[:username], :password=>obj[:password], :ts=>obj[:obj]||ts})
157
+
158
+ _servers[ip].run! cmd
159
+ end
160
+
161
+ yield
162
+
163
+ _servers.each do |k,v|
164
+ v.stop!
165
+ v.collect
166
+ end
167
+
168
+ end
169
+
170
+ end
171
+
172
+ end
173
+
174
+
data/lib/run.rb ADDED
@@ -0,0 +1,113 @@
1
+ #After roadRunner.run() executed,
2
+ #execute roadRunner.report() show the Performance report
3
+
4
+ module RoadRunnerModule
5
+ def run
6
+ @strat_time = Time.now
7
+ @initBlk.call
8
+ @thread_pool = {}
9
+ @counter = 0
10
+
11
+ @log.debug "Mode => #{@mode}"
12
+ iterationBlk = case @mode
13
+ when /thread/,/t/ then
14
+ proc {
15
+ @users.times do |userId|
16
+ @thread_pool[Thread.start(){
17
+ @iterations.times do |iterationId|
18
+
19
+ rcost = 0
20
+ @transaction_blk.each_pair{|k,v|
21
+ rcost = self.transaction(k,v)
22
+ }
23
+
24
+ # rcost = self.transaction('Action',&@actBlk)
25
+ self.log.debug "IterationID is #{self.iterationId};UserID is #{self.userId};This Action Cost #{rcost} seconds"
26
+ @longest = (@longest<rcost)?rcost:@longest
27
+ @thread_pool[Thread.current][:iterationId] = iterationId
28
+ end
29
+ @counter += 1
30
+ }]={:userId=>userId}
31
+ end
32
+ # @thread_pool.keys.each{|t|t.join}
33
+ while @counter != @users do
34
+ Thread.pass
35
+ end
36
+ }
37
+ when /process/,/p/ then
38
+ proc {
39
+ ppid=Process.pid
40
+ @log.info("Main Process pid => #{ppid}")
41
+
42
+ pids=[]
43
+ @users.times { |userId|
44
+ pids << Process.fork {
45
+ @userId=userId
46
+ @iterations.times do |iterationId|
47
+ @iterationId=iterationId
48
+
49
+ rcost = 0
50
+ @transaction_blk.each_pair{|k,v|
51
+ rcost = self.transaction(k,v)
52
+ }
53
+
54
+ # rcost = self.transaction('Action',&@actBlk)
55
+ @longest = (@longest<rcost)?rcost:@longest
56
+ end
57
+ @log.info("<PID:#{Process.pid}> going down.Longest job cost #{@longest}")
58
+
59
+ Process.kill("HUP", ppid)
60
+ }
61
+ }
62
+
63
+ switch=true
64
+ c=0
65
+ Signal.trap("HUP", proc { @log.info("One User(Process) done."); (switch = false;p "Main process<#{Process.pid}> going down.") if ((c+=1) == @users) })
66
+
67
+ @log.info "Waiting Processes => #{pids.inspect}"
68
+
69
+ p "Processes<#{pids.inspect}> working."
70
+ while switch
71
+ STDOUT.puts '.'
72
+ sleep 5
73
+ end
74
+ p $/
75
+
76
+ Process.waitall
77
+ @log.info "Processes down."
78
+ }
79
+ else
80
+ proc {
81
+ @iterations.times do |iterationId|
82
+ @iterationId=iterationId
83
+ @users.times do |userId|
84
+ @userId=userId
85
+
86
+ rcost = 0
87
+ @transaction_blk.each_pair{|k,v|
88
+ rcost = self.transaction(k,v)
89
+ }
90
+
91
+ # rcost = self.transaction('Action',&@actBlk)
92
+
93
+ # => the below sentence cost a lot of system resource
94
+ # => if you run for production result,keep it annotated!!!
95
+ # self.log.debug "IterationID is #{self.iterationId};UserID is #{self.userId};This Action Cost #{rcost} seconds"
96
+ @longest = (@longest<rcost)?rcost:@longest
97
+ end
98
+ end
99
+ }
100
+ end
101
+
102
+ p ' '+"RoadRunner".center(50, '*')
103
+ p ' *'+"---Run , on your way.".rjust(48, ' ')+'*'
104
+ p ' '+'*'*50
105
+ p
106
+ p " Running......"
107
+ @rep = Benchmark::measure(&iterationBlk)
108
+ p " Ending......"
109
+ @endBlk.call
110
+
111
+ @tps = @iterations*@users/@rep.real
112
+ end
113
+ end