Mac launchd and launchctl — the OSX alternative to cron

August 28, 2008

I was revisiting my metrics project, having used the first one as the prototype to refine requirements (nothing works better at getting real requirements out of people than showing them something that doesn’t quite do what they want).

When it came time to test a monitor, I tried to get one running under cron and it didnt actually work for me. I can’t remember if cron has ever worked for me on a mac, but didn’t have the time to figure out why and how. It was time to make the jump to launchd.

Launchd is billed as anĀ  init.d, /etc/rc, xinetd, .profiile, and crontab replacement, i.e. it can launch scripts at system startup, user login, or on a specified interval.

My use case was to do something cron like. This was not entirely straightforward, there is a difference between using StartCalendarInterval (to run things on a specified date, or every minute if no value is specified) and StartInterval (to run things at a specified interval, similar to specifying */5 for every 5 minutesin cron).

programs are loaded into launchd with launchctl, they are specified as plist files with a pretty simple key/value and/or key/dictionary of values XML format. Here is my .plist file for running something every 5 minutes:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
     <dict>
       <key>Label</key>
       <string>com.evri.metrics.deployment.cron</string>
       <key>ProgramArguments</key>
       <array>
          <string>/opt/local/bin/ruby</string>
          <string>/Users/arunjacob/hypertext/metrics_monitor/lib/tasks/deployment_monitoring/deployment_monitor_driver.rb</string>
          <string>deployment_aggregator</string>
       </array>
       <key>StandardErrorPath</key>
       <string>/dev/null</string>
       <key>StandardOutPath</key>
       <string>/dev/null</string>
       <key>StartInterval</key>
       <integer>300</integer>
       <key>RunAtLoad</key>
       <true/>
     </dict>
</plist>

Note that in key value parlance, StartInterval takes an integer which specifies the # of seconds. If I wanted to run something every day at a specified time, I would use StartCalendarInterval, which takes a dictionary element that contains time intervals.

<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
	"http://www.apple.
	com/DTDs/PropertyList-1.0.dtd">
	<plist version="1.0">
	<dict>
		<key>Label</key>
		<string>com.apple.periodic-daily</string>
		<key>ProgramArguments</key>
		<array>
			<string>/usr/sbin/periodic</string>
			<string>daily</string>
		</array>
		<key>LowPriorityIO</key>
		<true/>
		<key>Nice</key>
		<integer>1</integer>
		<key>StartCalendarInterval</key>
		<dict>
			<key>Hour</key>
			<integer>3</integer>
			<key>Minute</key>
			<integer>15</integer>
		</dict>
	</dict>
	</plist>

Note the difference between StartCalendarInterval syntax and StartInterval syntax — StartCalendarInterval takes a dict structure that contains key/value pairs. In other words it takes a hash. You can also use Arrays, as specified in the value for the ProgramArguments key. Just make sure your keys have the correct kind of values. as specified here.

Advertisements

More Rails-tarded ness: named resources

August 21, 2008

I was showing my monitoring app to a co-worker, who wanted to access some of the resources by URLs that contained their names. Hey, that actually makes sense! He wants to refer to resources by their actual names — brilliant. Unfortunately for my lazy ass, this is a departure from the standard rails resource routing conventions, where

map.resources :{controller name}

automagically generates routing like this:

/controller name/:id

I wanted to have both approaches, mainly because I’m lazy and dont want to rework my code that navigates back to these resources by ID. My first attempt at doing this was to put a custom named resource in front of my default map.resources statement:

map.named_monitor_instances ‘monitor_instances/:name’, :controller=>’monitor_instances’, :action=>’show_named_monitors’

this resulted in me getting a ‘missing template for show_named_monitors’ message, which was fine. I didn’t want to render the same view in another erb file.

The best solution I’ve found for having it both ways is by realizing that the default route :id parameter is just a parameter, and can contain a name as well as a number. Other named routes can be quite specific about what they contain, but the default route is pretty forgiving. I modified the controller code to look like this:

begin
@monitor_instance = MonitorInstance.find(params[:id])
rescue

@monitor_instance = MonitorInstance.find_by_name(params[:id])
end

to catch the instance where the find_by_id(‘foo’) fails and try to find foo by name. Graceful? No. Elegant? Not really. I’m sure this level of rails-tardedness will get me flamed by Rails Zealots who think I’ve gone and dicked up a perfectly elegant solution. But is it easy? Hell to the Yeah it is.