Friday, 21 September 2012

Process Watchman

Update: If the code it is running completes before the maximum time, it will block the program from completing until the time runs out. Still needs work! (Read: It doesn't work XD)

I was looking for a process monitor, sort of like monit, or god but these two solutions are for keeping a process alive or restarting a process if it is misbehaving. What I wanted was a simple process watcher that would simply kill a process if it misbehaves or runs out of time, while returning the completed or partial output of the process it was watching.

For this I quickly wrote a small tool to do this on *nix machines.
It is quite hacked together so I must apologise for the mess, it is in its first version here and the program was written as fast as I could imagine without prior design. There will be numerable ways of improving this tool.

Currently it accepts 3 args, a command, an integer value for seconds and a float value for ram megabytes. There is probably a problem with commands that are more than one word long which is most of them, experimentation and improvements are needed.

  1. #Author: Greg Myers
  2. #Date: 30/08/12
  3. @command = ARGV[0]
  4. @time_limit = ARGV[1].to_i #seconds
  5. @ram_limit = ARGV[2].to_f #megabytes
  6.  
  7. def watch_time(sec)
  8. exception = Class.new(Interrupt)
  9. begin
  10. x = Thread.current #x will contain whatever runs in yield
  11. y = Thread.start { #y watches and causes x to throw when timeout.
  12. begin
  13. sleep(sec)
  14. rescue => e #This raises any error x naturally hits
  15. x.raise e
  16. else #This executes if no exceptions happened until now
  17. x.raise "Process ran out of time to execute."
  18. end
  19. }
  20. return yield(sec)
  21. ensure
  22. if y
  23. y.kill
  24. y.join
  25. end
  26. end
  27. end
  28.  
  29. def watch_ram(megabytes, pid)
  30. exception = Class.new(Interrupt)
  31. begin
  32. x = Thread.current #x will contain whatever runs in yield
  33. y = Thread.start { #y watches and causes x to throw when timeout.
  34. begin
  35. loop {
  36. rss_use = `ps -o rss= -p #{pid}`.to_i #use ps to get rss of pid
  37. raise "Hit Ram Limit #{megabytes*1024}kb, with #{rss_use}kb" if megabytes*1024 < rss_use
  38. sleep(0.5)
  39. }
  40. rescue => e #This raises any error x naturally hits
  41. x.raise e
  42. end
  43. }
  44. return yield
  45. ensure
  46. if y
  47. y.kill
  48. y.join
  49. end
  50. end
  51. end
  52.  
  53. def get_payload_child_pid(pid)
  54. pipe = IO.popen("ps -ef | grep #{pid}")
  55. pipe.readlines[2] =~ /\w+\s+(?\d+)\s+(?\d+)/ #Always line 3, Line 1 = spawn cmd, Line 2 = IO.popen, Line 3 = ruby, Line 4 = grep
  56. pipe.close
  57. return $~[:child_pid]
  58. end
  59. if @command
  60. if @time_limit > 1
  61. if @ram_limit > 0
  62. watch_time(@time_limit){
  63. require 'pty'
  64. PTY.spawn("#{@command} 2>&1") do |r,w,p|
  65. child_pid = get_payload_child_pid(p)
  66. watch_ram(@ram_limit, child_pid){ loop { puts r.gets } }
  67. end
  68. }
  69. else
  70. puts "Invalid memory limit #{ARGV[2]}"
  71. end
  72. else
  73. puts "Invalid time limit #{ARGV[1]}"
  74. end
  75. else
  76. puts "Invalid command #{ARGV[0]}"
  77. end
I will be putting this on github publicly shortly.

Multicore programming with ruby

As I understand it multicore programming is about the ability to utilize multiple CPUs.
Also I hear a lot of people saying ruby cannot use multiple CPUs.

This is a load of rubbish if you are on a *nix system.

All you have to do if you want to use multiple CPUs is fork{} and it is running in its own process.
You can leave whatever is in the block to run and reap the process later, or detatch it if you do not need to see the results.

Fork returns the PID of the process it spawns, and used with Process.waitpid(), you can make the main process wait for the results to come back from the child. If you want the results yourself, you can just open a pipe between the child and the main process with IO.pipe

Simple.

read, write = IO.pipe
pid = fork do
  write.puts "test"
end
Process.waitpid(pid)
write.close
puts read.read
read.close



Resources:
http://www.ruby-doc.org/core-1.9.3/IO.html
http://www.ruby-doc.org/core-1.9.3/Process.html
Example based on:
http://stackoverflow.com/questions/1076257/returning-data-from-forked-processes