Module: 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)

Examples:

Usage:

class LS 
  include Runnable

  executes :ls
  command_style :extended
end

ls = LS.new
ls.alh
ls.run

Defined Under Namespace

Modules: ClassMethods

Accessors for the module class variables collapse

HERTZ =

Constant to calculate cpu usage.

100

Accessors for the module class variables collapse

Accessors for the module class variables collapse

Class Method Summary collapse

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.

Examples:

Valid calls:

find.depth                                         #=> find -depth
find.iname( '"*.rb"')                              #=> find -iname "*.rb"
find.foo( { :iname => '"*.rb"', :type => '"f"' } ) #=> find -iname "*.rb" - type "f"

Invalid calls:

sleep.5 #=> Incorrect. "5" is not a valid call to a ruby method so method_missing will not be invoked and will
raise a tINTEGER exception

Parameters:

  • method (Symbol)

    Method called that is missing

  • params (Array)

    Params in the call

  • block (Block)

    Block code in method

Returns:

  • (nil)


364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/runnable.rb', line 364

def method_missing( method, *params, &block )
  @command_line_interface ||= Object.const_get( command_style.to_s.capitalize.to_sym ).new

  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

#groupObject (readonly)

Process group.



127
128
129
# File 'lib/runnable.rb', line 127

def group
  @group
end

#log_pathObject

Process log output



137
138
139
# File 'lib/runnable.rb', line 137

def log_path
  @log_path
end

#optionsObject

Process options



135
136
137
# File 'lib/runnable.rb', line 135

def options
  @options
end

#outputObject

Process output



132
133
134
# File 'lib/runnable.rb', line 132

def output
  @output
end

#ownerObject (readonly)

Process owner.



125
126
127
# File 'lib/runnable.rb', line 125

def owner
  @owner
end

#pidObject (readonly)

Process id.



123
124
125
# File 'lib/runnable.rb', line 123

def pid
  @pid
end

#pwdObject (readonly)

Directory where process was called from.



129
130
131
# File 'lib/runnable.rb', line 129

def pwd
  @pwd
end

Class Method Details

.included(klass) ⇒ Object



38
39
40
# File 'lib/runnable.rb', line 38

def self.included(klass)
  klass.extend ClassMethods
end

.processesHash

List of runnable instances running on the system.

Returns:

  • (Hash)

    Using process pids as keys and instances as values.



383
384
385
# File 'lib/runnable.rb', line 383

def self.processes
  @@processes
end

Instance Method Details

#bandwidth(iface, sample_lapse = 0.1) ⇒ Number

Estimated bandwidth in kb/s.

Parameters:

  • iface (String)

    Interface to be scaned.

  • sample_time (Number)

    Time passed between samples in seconds. The longest lapse the more accurate stimation.

Returns:

  • (Number)

    The estimated bandwidth used.



334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/runnable.rb', line 334

def bandwidth( iface, sample_lapse = 0.1 )
  file = "/proc/#{@pid}/net/dev"
  File.open( file ).read =~ /#{iface}:\s+(\d+)\s+/
  init = $1.to_i
  
  sleep sample_lapse

  File.open( file ).read =~ /#{iface}:\s+(\d+)\s+/
  finish = $1.to_i

  (finish - init)*(1/sample_lapse)/1024
end

#commandString

Default command to be executed

Returns:

  • (String)

    Command to be executed



149
150
151
# File 'lib/runnable.rb', line 149

def command
  self.class.to_s.split( "::" ).last.downcase
end

#command_styleSymbol

Parameter style used for the command.

Returns:

  • (Symbol)

    Command style.



143
144
145
# File 'lib/runnable.rb', line 143

def command_style
  :gnu
end

#cpuNumber

Estimated CPU usage in %.

Returns:

  • (Number)

    The estimated cpu usage.



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/runnable.rb', line 297

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

#input=(opt) ⇒ Object

Sets the command input to be passed to the command execution

Parameters:

  • opt (String)

    Command input



279
280
281
# File 'lib/runnable.rb', line 279

def input=( opt )
  @command_input = opt
end

#joinnil

Wait for command thread to finish it execution.

Returns:

  • (nil)


254
255
256
257
# File 'lib/runnable.rb', line 254

def join
  @run_thread.join if @run_thread.alive?
  @output unless @output.empty?
end

#killnil

TODO:

Raise an exeption if process is not running.

Kill the comand.

Returns:

  • (nil)


244
245
246
247
248
249
250
# File 'lib/runnable.rb', line 244

def kill
  send_signal( :kill )

  # In order to maintain consistency of @@processes
  # we must assure that @run_thread finish correctly
  join
end

#memNumber

Calculate the estimated memory usage in Kb.

Returns:

  • (Number)

    Estimated mem usage in Kb.



291
292
293
# File 'lib/runnable.rb', line 291

def mem
  File.open( "/proc/#{@pid}/status" ).read.split( "\n" )[11].split( " " )[1].to_i
end

#run(name = nil, opts = nil, log_path = nil) ⇒ nil

Start the execution of the command.

Returns:

  • (nil)


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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/runnable.rb', line 158

def run(name = nil, opts = nil, log_path = nil)
  return false if @pid
  # Create a new mutex
  @pid_mutex = Mutex.new
  
  # Log path should be an instance variable to avoid a mess
  @log_path = log_path || @log_path

  # Create pipes to redirect Standar I/O
  out_rd, out_wr = IO.pipe
  # Redirect Error I/O
  err_rd, err_wr = IO.pipe

  # Reset exceptions array to not store exceptions for
  # past executions
  command_argument = opts ? opts.split(" ") : compose_command

  @pid = Process.spawn( command.to_s, *command_argument, { :out => out_wr, :err => err_wr } )

  # Include instance in class variable
  self.class.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
  stream_info = {
    :out => [out_wr, out_rd],
    :err => [err_wr, err_rd]
  }

  if name
    cmd_info = self.class.commands[name]
    stream_processors = {
      :outputs => cmd_info[:outputs],
      :exceptions => cmd_info[:exceptions]
    }
  end

  output_threads = process_streams( stream_info, stream_processors )

  # Create a new thread to avoid blocked processes
  @run_thread = threaded_process(@pid, output_threads)

  # 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 Errno::ENOENT
    # 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
  rescue #Errno::EACCESS
    @pwd = Dir.getwd
  end
end

#running?Bool

Check if prcess is running on the system.

Returns:

  • (Bool)

    True if process is running, false if it is not.



261
262
263
# File 'lib/runnable.rb', line 261

def running?
  Dir.exists?( "/proc/#{@pid}") 
end

#send_signal(signal) ⇒ Object

TODO:

raise ESRCH if pid is not in system or EPERM if pid is not from user.

Send the desired signal to the command.

Parameters:

  • Signal (Symbol)

    to be send to the command.



391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/runnable.rb', line 391

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

#std_errString

Standar error output of the command

Returns:

  • (String)

    Standar error output



273
274
275
# File 'lib/runnable.rb', line 273

def std_err
  @std_err ||= ""
end

#std_outString

Standar output of command

Returns:

  • (String)

    Standar output



267
268
269
# File 'lib/runnable.rb', line 267

def std_out
  @std_out ||= ""
end

#stopnil

TODO:

Raise an exception if process is not running.

Stop the command.

Returns:

  • (nil)


233
234
235
236
237
238
239
# File 'lib/runnable.rb', line 233

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