Class: Runnable
- Inherits:
-
Object
- Object
- Runnable
- Defined in:
- lib/runnable.rb
Overview
Convert a executable command in a Ruby-like class you are able to start, define params and send signals (like kill, or stop)
Constant Summary collapse
- HERTZ =
Constant to calculate cpu usage.
100
- @@processes =
List of runnable instances running on the system order by pid.
Hash.new
Instance Attribute Summary collapse
-
#group ⇒ Object
readonly
Process group.
-
#owner ⇒ Object
readonly
Process owner.
-
#pid ⇒ Object
readonly
Process id.
-
#pwd ⇒ Object
readonly
Directory where process was called from.
Class Method Summary collapse
-
.command_style(style) ⇒ nil
Define the parameter style to be used.
-
.processes ⇒ Hash
List of runnable instances running on the system.
Instance Method Summary collapse
-
#command_style ⇒ Symbol
Parameter style used for the command.
-
#cpu ⇒ Number
Estimated CPU usage in %.
-
#exceptions ⇒ Hash
abstract
Returns a hash of regular expressions and exceptions associated to them.
-
#failed(exceptions) ⇒ nil
abstract
Method called when command executions fail.
-
#finish ⇒ nil
abstract
Method called when command ends with no erros.
-
#initialize(option_hash = {}) ⇒ Runnable
constructor
Create a new instance of a runnable command.
-
#input(param) ⇒ nil
Set the input files.
-
#join ⇒ nil
Wait for command thread to finish it execution.
-
#kill ⇒ nil
Kill the comand.
-
#mem ⇒ Number
Calculate the estimated memory usage in Kb.
-
#method_missing(method, *params, &block) ⇒ nil
Convert undefined methods (ruby-like syntax) into parameters to be parsed at the execution time.
-
#output(param) ⇒ nil
Set the output files.
-
#run ⇒ nil
Start the execution of the command.
- #running? ⇒ Boolean
-
#send_signal(signal) ⇒ Object
Send the desired signal to the command.
-
#stop ⇒ nil
Stop the command.
Constructor Details
#initialize(option_hash = {}) ⇒ Runnable
Create a new instance of a runnable command.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/runnable.rb', line 71 def initialize( option_hash = {} ) # keys :delete_log # :command_options # :log_path # If we have the command class in a namespace, we need to remove # the namespace name @command = self.class.to_s.split( "::" ).last.downcase # Set the default command option # Empty by default option_hash[:command_options] ||= "" @options = option_hash[:command_options] # Set the log path # Default path is "/var/log/runnable" option_hash[:log_path] ||= "/var/log/runnable/" @log_path = option_hash[:log_path] # Set the delete_log option # true by default if option_hash[:delete_log] == nil @delete_log = true else @delete_log = option_hash[:delete_log] end # Store input options @input = Array.new # Store output options @output = Array.new # @todo: checks that command is in the PATH # ... # we dont set the pid, because we dont know until run @pid = nil @excep_array = [] # Metaprogramming part # Create a new instance of the parser class @command_line_interface = Object.const_get( command_style.to_s.capitalize.to_sym ).new # End Metaprogramming part #End of initialize instance variables create_log_directory end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *params, &block) ⇒ nil
Convert undefined methods (ruby-like syntax) into parameters to be parsed at the execution time. This only convert methods with zero or one parameters. A hash can be passed and each key will define a new method and method name will be ignored.
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/runnable.rb', line 302 def method_missing( method, *params, &block ) if params.length > 1 super( method, params, block ) else if params[0].class == Hash # If only one param is passed and its a Hash # we need to expand the hash and call each key as a method with value as params # @see parse_hash for more information parse_hash( params[0] ) else @command_line_interface.add_param( method.to_s, params != nil ? params.join(",") : nil ) end end end |
Instance Attribute Details
#group ⇒ Object (readonly)
Process group.
40 41 42 |
# File 'lib/runnable.rb', line 40 def group @group end |
#owner ⇒ Object (readonly)
Process owner.
38 39 40 |
# File 'lib/runnable.rb', line 38 def owner @owner end |
#pid ⇒ Object (readonly)
Process id.
36 37 38 |
# File 'lib/runnable.rb', line 36 def pid @pid end |
#pwd ⇒ Object (readonly)
Directory where process was called from.
42 43 44 |
# File 'lib/runnable.rb', line 42 def pwd @pwd end |
Class Method Details
.command_style(style) ⇒ nil
Define the parameter style to be used.
48 49 50 51 52 |
# File 'lib/runnable.rb', line 48 def self.command_style( style ) define_method( :command_style ) do style end end |
.processes ⇒ Hash
List of runnable instances running on the system.
320 321 322 |
# File 'lib/runnable.rb', line 320 def self.processes @@processes end |
Instance Method Details
#command_style ⇒ Symbol
Parameter style used for the command.
56 57 58 |
# File 'lib/runnable.rb', line 56 def command_style :gnu end |
#cpu ⇒ Number
Estimated CPU usage in %.
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/runnable.rb', line 238 def cpu # Open the proc stat file begin stat = File.open( "/proc/#{@pid}/stat" ).read.split # Get time variables # utime = User Time # stime = System Time # start_time = Time passed from process starting utime = stat[13].to_f stime = stat[14].to_f start_time = stat[21].to_f # uptime = Time passed from system starting uptime = File.open( "/proc/uptime" ).read.split[0].to_f # Total time that the process has been executed total_time = utime + stime # in jiffies # Seconds passed between start the process and now seconds = uptime - ( start_time / HERTZ ) # Percentage of used CPU ( ESTIMATED ) (total_time / seconds.to_f) rescue IOError # Fails to open file 0 rescue ZeroDivisionError # Seconds is Zero! 0 end end |
#exceptions ⇒ Hash
This method should be overwritten in child classes.
Returns a hash of regular expressions and exceptions associated to them. Command output is match against those regular expressions, if it does match an appropiate exception is included in the return value of execution.
338 339 340 |
# File 'lib/runnable.rb', line 338 def exceptions {} end |
#failed(exceptions) ⇒ nil
Method called when command executions fail. This method is a hook so it should be overwritten in child classes.
354 355 |
# File 'lib/runnable.rb', line 354 def failed( exceptions ) end |
#finish ⇒ nil
Method called when command ends with no erros. This method is a hook so it should be overwritten in child classes.
346 347 |
# File 'lib/runnable.rb', line 346 def finish end |
#input(param) ⇒ nil
Set the input files.
274 275 276 |
# File 'lib/runnable.rb', line 274 def input( param ) @input << param end |
#join ⇒ nil
Wait for command thread to finish it execution.
222 223 224 |
# File 'lib/runnable.rb', line 222 def join @run_thread.join if @run_thread.alive? end |
#kill ⇒ nil
Raise an exeption if process is not running.
Kill the comand.
212 213 214 215 216 217 218 |
# File 'lib/runnable.rb', line 212 def kill send_signal( :kill ) # In order to maintain consistency of @@processes # we must assure that @run_thread finish correctly join end |
#mem ⇒ Number
Calculate the estimated memory usage in Kb.
232 233 234 |
# File 'lib/runnable.rb', line 232 def mem File.open( "/proc/#{@pid}/status" ).read.split( "\n" )[11].split( " " )[1].to_i end |
#output(param) ⇒ nil
Set the output files.
281 282 283 |
# File 'lib/runnable.rb', line 281 def output( param ) @output << param end |
#run ⇒ nil
Start the execution of the command.
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/runnable.rb', line 124 def run # Create a new mutex @pid_mutex = Mutex.new # Create pipes to redirect Standar I/O out_rd, out_wr = IO.pipe # Redirect Error I/O err_rd, err_wr = IO.pipe @pid = Process.spawn( "#{@command} #{@input.join( " " )} \ #{@options} #{@command_line_interface.parse} \ #{@output.join( " " )}", { :out => out_wr, :err => err_wr } ) # Include instance in class variable @@processes[@pid] = self # Prepare the process info file to be read file_status = File.open( "/proc/#{@pid}/status" ).read.split( "\n" ) # Owner: Read the owner of the process from /proc/@pid/status @owner = file_status[6].split( " " )[1] # Group: Read the Group owner from /proc/@pid/status @group = file_status[7].split( " " )[1] # Set @output_thread with new threads # wich execute the input/ouput loop create_logs(:out => [out_wr, out_rd], :err => [err_wr, err_rd]) # Create a new thread to avoid blocked processes @run_thread = Thread.new do # Wait to get the pid process even if it has finished Process.wait( @pid, Process::WUNTRACED ) # Wait each I/O thread @output_threads.each { |thread| thread.join } # Delete log if its necesary delete_log # Get the exit code from command exit_status = $?.exitstatus # In case of error add an Exception to the @excep_array @excep_array << SystemCallError.new( exit_status ) if exit_status != 0 # Call methods according to the exit code if @excep_array.empty? finish else failed( @excep_array ) end # This instance is finished and we remove it @@processes.delete( @pid ) end # Satuts Variables # PWD: Current Working Directory get by /proc/@pid/cwd # @rescue If a fast process is runned there isn't time to get # the correct PWD. If the readlink fails, we retry, if the process still alive # until the process finish. begin @pwd = File.readlink( "/proc/#{@pid}/cwd" ) rescue # If cwd is not available rerun @run_thread if @run_thread.alive? #If it is alive, we retry to get cwd @run_thread.run retry else #If process has terminated, we set pwd to current working directory of ruby @pwd = Dir.getwd end end end |
#running? ⇒ Boolean
226 227 228 |
# File 'lib/runnable.rb', line 226 def running? Dir.exists?( "/proc/#{@pid}") end |
#send_signal(signal) ⇒ Object
raise ESRCH if pid is not in system or EPERM if pid is not from user.
Send the desired signal to the command.
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
# File 'lib/runnable.rb', line 361 def send_signal( signal ) if signal == :stop signal = :SIGINT elsif signal == :kill signal = :SIGKILL end `ps -ef`.each_line do |line| line = line.split pid = line[1] ppid = line[2] if ppid.to_i == @pid Process.kill( signal, pid.to_i ) end end begin Process.kill( signal, @pid ) rescue Errno::ESRCH # As we kill child processes, main process may have exit already end end |
#stop ⇒ nil
Raise an exception if process is not running.
Stop the command.
201 202 203 204 205 206 207 |
# File 'lib/runnable.rb', line 201 def stop send_signal( :stop ) # In order to maintain consistency of @@processes # we must assure that @run_thread finish correctly @run_thread.run if @run_thread.alive? end |