I was recently asked if I had the content of some articles that I posted a long time ago on a blog I used to run. After some searching I managed to scrape together the content using the Wayback Machine. It's faithfully recreated here without changes, something I should have done when I first bought the barkingiguana.com domain.
For today’s adventure in Ruby, I’m going to write a simple daemon process. To start with, it won’t do anything particularly useful – every second it’ll print the current time to STDOUT.
Once that’s working, I’ll swap in the socket-checking code from my earlier posts and bump the interval to 15 seconds.
A simple time-printing daemon
kawaii:~ craig$ irb
irb(main):001:0> fork do # Fork a new process
irb(main):002:1* while true # Loop forever
irb(main):003:2> puts Time.now # Print the time
irb(main):004:2> sleep 1 # Sleep for a second
irb(main):005:2> end # while true
irb(main):006:1> end # fork
=> 15738
irb(main):007:0> Sat Jun 03 11:31:09 BST 2006
Sat Jun 03 11:31:10 BST 2006
Sat Jun 03 11:31:11 BST 2006
Easy. The fork call creates a child process that runs independently, and we get back a PID. Meanwhile, the parent IRB session carries on as normal (well, with timestamps appearing in the background).
Monitoring a socket
Next, let’s check that Postfix is listening on port 25 on the secondary MX, mx2.xeriom.net. Don’t forget to require the socket library – otherwise you’ll always hit the rescue block. Ask me how I know.
irb(main):045:0> require 'socket'
=> true
irb(main):046:0> fork do
irb(main):047:1* while true
irb(main):048:2> begin
irb(main):049:3* t = TCPSocket.open('mx2.xeriom.net', 'smtp')
irb(main):050:3> puts Time.now.to_s + ": MX2 is listening on port 25."
irb(main):051:3> t.close
irb(main):052:3> rescue
irb(main):053:3> puts Time.now.to_s + ": MX2 is NOT listening on port 25."
irb(main):054:3> end
irb(main):055:2> sleep 15
irb(main):056:2> end
irb(main):057:1> end
=> 15759
irb(main):058:0> Sat Jun 03 11:48:25 BST 2006: MX2 is listening on port 25.
Sat Jun 03 11:48:41 BST 2006: MX2 is listening on port 25.
Sat Jun 03 11:48:56 BST 2006: MX2 is NOT listening on port 25.
Sat Jun 03 11:49:11 BST 2006: MX2 is listening on port 25.
That third check caught Postfix being momentarily unavailable. Useful.
Monitoring multiple hosts and ports
That was a little too easy, so let’s extend the problem. This time we’ll check an arbitrary number of ports across an arbitrary number of hosts, using threads inside the forked process for concurrency:
irb(main):107:0> host_sockets = { 'mx2.xeriom.net' => [ 25 ], 'kiwi.xeriom.net' => [ 21, 22, 25 ], 'guava.xeriom.net' => [ 21, 22, 25 ], 'mx1.xeriom.net' => [ 25 ] }
=> {'mx2.xeriom.net' => [ 25 ], 'kiwi.xeriom.net' => [ 21, 22, 25 ], 'guava.xeriom.net' => [ 21, 22, 25 ], 'mx1.xeriom.net' => [ 25 ]}
irb(main):108:0> fork do
irb(main):109:1* while true
irb(main):110:2> host_sockets.each { |hostname, sockets|
irb(main):111:3* Thread.new(hostname, sockets) { |host, socks|
irb(main):112:4* socks.each { |socket|
irb(main):113:5* begin
irb(main):114:6* t = TCPSocket.new(host, socket)
irb(main):115:6> puts Time.now.to_s + ": " + host.to_s + " is listening on port " + socket.to_s
irb(main):116:6> t.close
irb(main):117:6> rescue
irb(main):118:6> puts Time.now.to_s + ": " + host.to_s + " is NOT listening on port " + socket.to_s
irb(main):119:6> end
irb(main):120:5> }
irb(main):121:4> }
irb(main):122:3> }
irb(main):123:2> sleep 15
irb(main):124:2> end
irb(main):125:1> end
=> 15784
Sat Jun 03 12:16:56 BST 2006: kiwi.xeriom.net is listening on port 21Sat Jun 03 12:16:56 BST 2006: mx2.xeriom.net is listening on port 22
Sat Jun 03 12:16:56 BST 2006: guava.xeriom.net is listening on port 22
Sat Jun 03 12:16:56 BST 2006: kiwi.xeriom.net is listening on port 22Sat Jun 03 12:16:56 BST 2006: mx2.xeriom.net is listening on port 25
Sat Jun 03 12:16:56 BST 2006: kiwi.xeriom.net is listening on port 25Sat Jun 03 12:16:56 BST 2006: guava.xeriom.net is listening on port 25 ...
Obviously, if I were scaling this to millions of hosts, a thread-per-host approach would collapse under its own weight. But for keeping an eye on a small network, it does the job nicely.