random walk through fields of musings

Tuesday, February 3, 2009

on calendars and things

Google calendar has become my preferred way of keeping track and coordinating events with my wife, family, cohousing community, friends, travel and almost anything else that is temporal it seems. The hard exception is work, which till recently used "Meeting Maker", a piece of bloatware that had no way to exchange calendars in any known, open format, but now is based on Microsoft Exchange.

Exchange doesn't have an ical feed (that Google Calendar can easily consume, and that I've set almost everything else up to produce for its' consumption), however Exchange sends event data as iCalendar attachments in emails. Thankfully, Google Mail (Gmail and mail for apps) recognizes the text/calendar attachments, parses the iCalendar info, and offers to add it to your Google Calendar.

Google Mail recognises .ics attachments by djfiander.

Amazingly, Exchange also will parse and allow Google Calendar invites (which are also iCalendar format) to be added (or refused) to my work calendar (I just had to get the correct email address to send it to for it to work!), there is a bit of broken-ness in dealing with replies to invitations from Google Calendar, but that I can bear to kludge by hand.

An well-documented, open, hardly novel way of exchanging calendar information by email is fantastic and I've added such attachments to the GO online meal signup and shift system, here's an example (part of a multipart/alternative message):


------=MIME_BOUNDRY_altpparts
Content-Type: text/calendar; charset="utf-8"; method=REQUEST
Content-Transfer-Encoding: 8bit
Content-Disposition: attachment; filename=invite.ics

BEGIN:VCALENDAR
PRODID:-//Grot Org//Meal tracker//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
DTSTART:20090203T231500Z
DTEND:20090204T000000Z
DTSTAMP:20090129T154454Z
ORGANIZER;CN=go-meal:mailto:foo@plzs.org
UID:1680@meals.plzs.org
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=FALSE;CN=Fitzgerald/Aditya;X-NUM-GUESTS=4:mailto:nobody@grot.org
CLASS:PRIVATE
CREATED:20090203T130300Z
DESCRIPTION:Wings Over Great Oak by tim details at https://example.com/meal-signup/show?m=1680
LAST-MODIFIED:20090129T154454Z
LOCATION:go-meal
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Wings Over Great Oak (go-meal)\, 4 diners
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:REMINDER
TRIGGER;RELATED=START:-PT15M
END:VALARM
END:VEVENT
END:VCALENDAR
------=MIME_BOUNDRY_altpparts--

Netapp OnTap per-volume statistics

I'm a fan of Netapps, and I've grown used to using the OnTap SNMP agent to collect statistics on various filer functions, stuff them into RRDs and troubleshoot using trend graphs (I really like drraw for that). I can then make cool graphs like:

drraw by you.

However, those ops numbers are for the filer as a whole making it hard to tell *what* is the target for all that activity -- we need per volume and even per disk stats to find hot spots. The "stats" command in OnTap can expose that data, however not via SNMP and only via the cli (maybe via the SDK, but that's a whole level of complication I'd like to avoid). The whole list of supported stats counters is exposed using the command stats list counters. So using ssh with public keys (and using stats start/stop every 5 minutes), I was able to "plug-in" the output from periodically querying the stats command into the existant set of RRDs that drraw draws from. Here's the per-volume read/write/other ops for the same filer:

drraw by you.

and one showing total time spent serving along with amount of data served in bytes:

drraw by you.

and bandwidth by volume:

drraw by you.
the relevant code fragment is quite simple and follows the SNMP_util::snmpmaptable semantics, each "row" of instance data is sent to a callback function which writes it into the appropriate RRD (with an example invocation):

my($getStatCols);

$getStatCols->{'volume'}->{'type'} = "volStats";
@{$getStatCols->{'volume'}->{'cols'}} = ("read_data", "read_latency", "read_ops",  "write_data", "write_latency", "write_ops", "other_latency", "other_ops");

for my $stat (keys %{$getStatCols}){
my($stattxt) = "${stat}:*:" . join(" ${stat}:*:", @{$getStatCols->{$stat}->{'cols'}});
getStatTable($username, $netappHostname, $getStatCols->{$stat}->{'type'}, $stattxt,  \&printfun);
}

sub getStatTable {
my($user, $host, $id, $statcmd, $callback) = @_;

my(@ret) = `ssh ${user}\@${host} "stats stop -I ${id} -O print_zero_values=off -c -d |"`;
my(@start) = `ssh ${user}\@${host} "stats start -I ${id} ${statcmd}"`;
my($startcmd) = join(' ', @start);
if ($startcmd){
   $startcmd =~ s/\s+//g;
   chomp($startcmd);
   if ($startcmd ne ""){
     print STDERR "ERROR starting stats collection for ${user}\@${host} $statcmd: " . $startcmd . "\n";
   }
}

if ($#ret <= 2){
   print STDERR "ERROR retrieving stats from ${user}\@${host} ${statcmd}: " . join(" ", @ret) . "\n";
} else {
   shift(@ret);
   shift(@ret);
   shift(@ret);
   for (my $i=0;$i<=$#ret;$i++){
     my($l) = $ret[$i];
     chomp($l);
     $l =~ s/^\s+//g;
     $l =~ s/\s+$//g;
     if ($l !~ /|$/){
       $l .= "|";
     }
     my(@cols) = ($id, $i, split(/\|/, $l));
     &$callback(@cols);
   }
 }
}

sub printfun {
#shift(@_);
my(@vals) = @_;
for (my $i=0;$i <= $#vals; $i++){
if (! $vals[$i] || $vals[$i] eq ''){
$vals[$i] = 0;
}
}
#next if (! @vals);
#next if (! $vals[0] || ! $vals[1]);
print join('|', @vals) . "|\n";
}

Tuesday, January 20, 2009

name the reason for that blip

See that blip in requests around noon EST today? Should be easy to tell what it was...



yes, all those UM students and faculty who turned away from CTools to watch the Obama inauguration. Never noticed that blip for the 43rd president, but then again it is probably due to the fact that CTools is pervasive enough now that we can use it instead of Nielsen ratings. Or maybe not.

Wednesday, December 24, 2008

photos side-by-side aka montages

I seem to do this infrequently enough that I need to make myself a reminder, luckily I wrote a script that is pretty self-explanatory -- in this example it puts two photos side by side (similar method can be used to make 6 2"x2" passport photographs print on a single 4x6 print -- much cheaper than going to one of those passport places).


#!/usr/bin/perl

use strict;
local($|) = 1;

use Image::Magick;
my(@file) = @ARGV;

my($rv);

my($image)=Image::Magick->new;

$rv = $image->Read(@file);
warn "$rv" if $rv;

my($montage) = $image->Montage(
tile=>'2x1',
geometry=>'1704x2272',
mattecolor=>'#FFFFFF', #does not take?
frame=>'10',
);

$rv = $montage->Write('mp.jpg');
warn "$rv" if "$rv";


If you have ImageMagick installed but not the perl bindings, you can do this on the command line too.

Tuesday, December 23, 2008

terminal reset and why enter doesn't return

occasionally, if for some reason, my ssh session to a remote machine, probably running screen, doesn't terminate cleanly, and I have to return-~. to kill it, my Mac OSX terminal gets "screwed up" -- the number of rows gets reduced, the characters don't all echo, etc.. Usually, running the reset command will fix things, but sometimes it doesn't help. Of course I can't find the reference now, but some site suggested entering ^jreset^j (ctrl-j reset ctrl-j) repeatedly till it works. Incredulous, I tried it, and indeed it does work!

Monday, December 15, 2008

searching UMOD with JNDI

For some of the support tools I've been writing for CTools (in JSP with JSTL) I need to authorize against a group in the LDAP interface to UMOD. JNDI is very complete, so it should have been an easy task, but searching to see if a given logged-in (via Cosign) user was in the requisite group wasn't working. I had some example code in Perl that a colleague had once sent out, and a 1-to-1 transcription into Java using JNDI didn't work. It turns out that the scope for searches in JNDI does not default to "SUB" AND if you try to bind (even anonymously) the query tries to ADD the attribute instead of searching for it, and so had to be explicitly set. To help you (and remind me) avoid this pain, here is the requisite code:


private boolean isMember(String grp, String un) throws Exception {
   boolean isMem = false;
   Hashtable env = new Hashtable();
   env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
   env.put(Context.PROVIDER_URL, "ldap://ldap.itd.umich.edu:389/dc=umich,dc=edu");

   try {
     javax.naming.directory.DirContext ctx = new javax.naming.directory.InitialDirContext(env);
     String[] attrIDs = {"member"};

     javax.naming.directory.SearchControls ctls = new javax.naming.directory.SearchControls();
     String filter = "(&(cn=" + grp + ") (objectclass=rfc822MailGroup))";
     ctls.setReturningAttributes(attrIDs);
     ctls.setReturningObjFlag(true);
     ctls.setSearchScope(javax.naming.directory.SearchControls.SUBTREE_SCOPE);
     String searchBase = "ou=Groups";
     javax.naming.NamingEnumeration s = ctx.search(searchBase, filter, ctls);
     String positiveMatch = "uid=" + un + ",";
     while (s.hasMore()) {
     javax.naming.directory.SearchResult sr = (javax.naming.directory.SearchResult)s.next();
     javax.naming.NamingEnumeration e = (sr.getAttributes()).getAll();
     while (e.hasMoreElements()){
       javax.naming.directory.Attribute attr = (javax.naming.directory.Attribute) e.nextElement();
       javax.naming.NamingEnumeration a = attr.getAll();
       while (a.hasMoreElements()){
         String val = (String) a.nextElement();
         if(val.indexOf(positiveMatch) != -1){
           isMem = true;
         }
       }
       a.close();
     }
     e.close();
   }
   s.close();
   ctx.close();
   return isMem;
  } catch (javax.naming.NamingException e) {
    System.err.println("Problem getting attribute:" + e);
    return false;
  }
}


There must be a better way of formatting code in HTML without JS.

Sunday, September 7, 2008

my second cohousing head cooking trial

Tandoori murgh boti kebab (Chicken)
by aditya


This will be a non-vegetarian North Indian complement to my mother's vegetarian South Indian meal.

Vegetarian option: sorry, there will be no vege entree (too much work
on the carne entree for this occasional head cook), though the sides
will be vege

Carnivore option: grilled, moderately spicy tandoori murgh boti kebab
(skewers of chicken thigh/breast pieces) Rice or Naan depending on the
number of people signed up.

Kid option: less spicy chicken kebabs

Side Dish: saag aloo (sauteed mixed greens and potatoes North Indian
style).

Salad: diced tomato, cucumber, onion with cumin, pepper and lime/lemon
dressing on the side

Dessert: fruit in season

Organic: where possible

Dairy: easy to avoid

Wheat/Gluten: easy to avoid

worked out to $5.84 per adult (scaled appropriately for teens and big kids) there were 30 adults and 10 kids in total.


cook with Tandoori murgh boti kebab

Tandoori murgh boti kebab