Saturday, August 22, 2015

SVC mini-script Testing

In a former life, I was a storage administrator for Thomas Jefferson University Hospital in Center City, Philadelphia. Aside from the wide array of lunch options we had to choose from, another thing I really enjoyed was working with IBM's SVC, or SAN Volume Controller. As a sysadmin, there are few software packages that you truly enjoy working with and you think are really, really good, but the SVC was one of those. One of the reasons was because the command-line interface was just a restricted Bash shell - something I knew very, very well. This allowed me to do pretty amazing things, as the shell is a wonderfully capable programming language. Back then, I had a Wiki that I used as a notepad, and I had several pages on the cool things you could do with SVC on the command line. Here is one of those pages, cut-and-pasted from my internal webserverBe warned, though - none of this has been updated since about 2007!

Thanks to the awesome Aussie Storage Blog for the inspiration to resurrect these old pages.

If you're not real familiar with bash and one-liner bash scripts, you might not feel comfortable running them on your SVC, considering the damage you can do to your SAN. A better idea might be to take a linux system that's running the bash shell, and test your miniscripts there.

Easy way

With any general user login, you won't have a restricted shell, but as long as you stick to builtin commands, it'll be effectively the same. The big problem you might have is not having access to the "svcinfo" or "svctask" commands. Here's a simple way to get around that... create simple shell scripts called "svcinfo" and "svctask".
Since svcinfo doesn't do anything except output information, you can make a very simple script:
#!/bin/bash

cat << EOF
0:DS4800:online:6:65:8445.0GB:256:2156.2GB
1:DS6800:online:1:20:772.5GB:256:68.5GB
2:DS4500:online:3:3:1628.2GB:256:928.2GB
3:DS4500-SATA:online:1:1:930.5GB:256:430.5GB
EOF
This will give the same output as the "svcinfo lsmdiskgrp -delim : -nohdr" command in SVC... since that's how I generated the output.
To get a little fancier, I could have it take different commands:
#!/bin/bash

mdiskgrp="0:DS4800:online:6:65:8445.0GB:256:2156.2GB
1:DS6800:online:1:20:772.5GB:256:68.5GB
2:DS4500:online:3:3:1628.2GB:256:928.2GB
3:DS4500-SATA:online:1:1:930.5GB:256:430.5GB"

# Names have been changed to protect the guilty
lsvdisk="0:Miata-data:1:io_grp1:online:0:DS4800:40.0GB:striped:::::60050768018F8148F800000000000094
1:Integra-db:0:io_grp0:online:0:DS4800:40.0GB:striped:::::60050768018F8148F800000000000095
2:WRX-data:1:io_grp1:online:0:DS4800:40.0GB:striped:::::60050768018F8148F800000000000096
3:WRX-data2:0:io_grp0:online:0:DS4800:15.0GB:striped:::::60050768018F8148F800000000000097
4:WRX-Archive:1:io_grp1:online:3:DS4500-SATA:500.0GB:striped:::::60050768018F8148F800000000000098"


case $1 in
    lsmdiskgrp)
        echo "$mdiskgrp"
        ;;
    lsvdisk)
        echo "$lsvdisk"
        ;;
esac
So, now if I run "svcinfo lsmdisk" I'll get five lines of output like I would get from "svcinfo lsvdisk -delim : -nohdr" on a very small SVC implementation. If I instead run "svcinfo lsmdiskgrp", I'll see the output I would have seen above.
We could get even fancier and make the script handle the options, like changing "-delim" and handling "-nohdr", but that's starting to get a bit too complicated for just testing.

Testing the script

Now that I have an actual "svcinfo" command, I can run miniscripts on my linux box:
svcinfo lsmdiskgrp -delim : -nohdr | while IFS=: read id name stat nummd numvd size extsize free
do
    [[ $name == *DS4500* ]] && echo "Mdiskgrp $name has $free free of $size"
done
  • I specified the "-delim : -nohdr" on my command line, so I could cut & Paste my tested miniscript to the actual SVC. The script above ignores anything past $1.
This will give us the following output:
Mdiskgrp DS4500 has 928.2GB free of 1628.2GB
Mdiskgrp DS4500-SATA has 430.5GB free of 930.5GB

What about svctask?

Well, with svctask, you're actually *doing* something, which you won't to in a test environment. One possible approach would be to make an svctask command which just tells you what would be run:
#!/bin/bash

echo "RUN: svctask $*"
Now, whenever our test miniscript calls the svctask command, we'll just see the command line that it would run:
svcinfo lsvdisk -delim : -nohdr | while IFS=: read id name iogid iogrp mdgid mdgrp rest
do
     [[ $mdgrp = DS4800 ]] && svctask chvdisk -name "DS4800-$name" "$name"
done
  • Note that I only grabbed variables I needed, leaving everything else (the rest of the data) in the variable "rest". This example would assign the fields from size to vdisk UID into "rest".
This script would find all vdisks from the mdiskgrp "DS4800" and rename them, prefixing their old name with "DS4800-". Using our "svctask" test script, we would get output like this:
RUN: svctask chvdisk -name "DS4800-Miata-data" "Miata-data"
RUN: svctask chvdisk -name "DS4800-Integra-db" "Integra-db"
RUN: svctask chvdisk -name "DS4800-WRX-data" "WRX-data"
RUN: svctask chvdisk -name "DS4800-WRX-data2" "WRX-data2"
That way we can check to make sure the commands that are output are what we would expect.
However, you might want to ask yourself how lucky you feel before you start scripting svctask commands, especially ones that can do damage. Something like this example, "chvdisk" renames, is pretty tame, so the possibility of doing major damage is low, but just use your brain and test first. Also, before you run the command on the SVC itself, make sure everything is right by prefixing your "svctask" command with "echo"... then you get the same output as you have above and nothing breaks.

SVC mini-script storage

In a former life, I was a storage administrator for Thomas Jefferson University Hospital in Center City, Philadelphia. Aside from the wide array of lunch options we had to choose from, another thing I really enjoyed was working with IBM's SVC, or SAN Volume Controller. As a sysadmin, there are few software packages that you truly enjoy working with and you think are really, really good, but the SVC was one of those. One of the reasons was because the command-line interface was just a restricted Bash shell - something I knew very, very well. This allowed me to do pretty amazing things, as the shell is a wonderfully capable programming language. Back then, I had a Wiki that I used as a notepad, and I had several pages on the cool things you could do with SVC on the command line. Here is one of those pages, cut-and-pasted from my internal webserver. Be warned, though - none of this has been updated since about 2007!

Thanks to the awesome Aussie Storage Blog for the inspiration to resurrect these old pages.

all of the miniscripts are copied via straight cut and paste so you'll have to scroll horizontally. Upside is you can cut and paste from here and into SVC.

very limited "grep" function

function grep { typeset my; while read my; do [[ $my == *$1* ]] && echo $my; done; }
Very rudimentary, just searches stdin for a simple match anywhere on the line, prints the line if it's a match.

Fancy monitor for MDiskGrp Migration with size and progress bar:

function progress { bar="########################################"; echo "Waiting: $(svcinfo lsmigrate | while read x y; do [[ $x = progress ]] && p=$y; [[ $x = migrate_source_vdisk_index ]] && vdisk=$y; if [[ $x = migrate_source_vdisk_copy_id ]]; then if [[ $p = 0 ]]; then echo -n "$vdisk "; else eval $(svcinfo lsvdisk $vdisk | while read a b; do [[ $a = name ]] && echo vdn=$b; [[ $a = real_capacity ]] && b=${b//.00/} && echo sz=$b; done ); printf "Vdisk %16s %3d%% of %7s [%-${#bar}s]\n" "$vdn" "${p}" "${sz}" "${bar:0:$((${#bar}*p/100))}" >&2; fi; fi; done)"; }

Show info about the vdisks assigned to a host

function lshostvdisk { [[ -z $1 ]] && return; FMT="%3s %-16s %6s %10s %7s %-7s\n"; echo "===== VDisks assigned to host $1 ====="; printf "$FMT" "ID" " VDisk" "Size" "MDiskGrp " "IOGrp " " UID"; svcinfo lshostvdiskmap -nohdr $1 | while read a a a vdid vd a; do svcinfo lsvdisk $vd | while read x y; do case $x in IO_group_name) iogrp=$y;; mdisk_grp_name) mdg=$y;; capacity) sz=${y/.??};; vdisk_UID) uid=${y:28};; grainsize) printf "$FMT" "$vdid" "$vd" "$sz" "$mdg" "$iogrp" "...$uid";; esac; done; done; }
Takes a single argument, returns a formatted list of vdisks assigned to the hosts, showing vdisk ID, name, size, mdiskgrp, iogrp, and the last four characters of the UID.

Match an arg to a host WWPN

function hostwwpn { [[ -z $1 ]] && echo Missing argument. && return; svcinfo lshost -nohdr | while read a h a a; do f=$(svcinfo lshost $h | grep $1); [[ -n $f ]] && echo $h $f; done; }

List dead hosts

function lsdeadhosts { svcinfo lshost -nohdr | while read a h a a; do z=$(svcinfo lshost $h | while read x y; do [[ $x = node_logged_in_count ]] && echo -n "$y"; [[ $x = state ]] && printf "/%-8s " "$y"; done ); [[ $z = *0* || $z == *inactive* ]] && printf "%-16s %-s\n" "$h" "$z"; done; }
This function searches through the host objects and looks for ones that have a zero in the "node_logged_in_count". It then displays the hosts as well as the node_logged_in_count numbers, and anything with all zeros is not currently connected to the SAN.

List live hosts

function lslivehosts { svcinfo lshost -nohdr | while read a h a a; do z=$(svcinfo lshost $h | while read x y; do [[ $x = node_logged_in_count ]] && echo -n "$y "; done ); [[ $z == *[1-9]* ]] && printf "%-16s %-s\n" "$h" "$z"; done; }
The converse of the above, useful if you have to quiesce the SAN and need to see who is still active.

basic "free" function to show mdiskgrp usage

function free() { FORMAT="%-12s %9s/%9s %5s\n"; printf "$FORMAT" "MDiskGrp" "Free" "Capacity" "Pct "; svcinfo lsmdiskgrp -delim " " -nohdr | while read a b c d e f g h i j k l m n o p; do pct="$((${i%.*}*1000/${f%.*}))"; printf "$FORMAT" "$b" "$h" "$f" "${pct%?}.${pct:((-1))}%"; done; }
Looks at the output of svcinfo lsmdiskgrp, displays free space, total capacity, and percentage. Fakes the integer math, if the units (GB/MB) aren't the same for free space and capacity, it'll break the math.

Migrate all extents from one mdisk to another:

sourcemdisk=10; targetmdisk=1; svcinfo lsmdiskextent -nohdr $sourcemdisk | while read vdisk extents copy; do echo "Starting migration of $extents extents of vdisk $vdisk to mdisk $targetmdisk"; svctask migrateexts -source $sourcemdisk -target $targetmdisk -exts $extents -vdisk $vdisk -threads 1; done
  • If there are a large number of vdisks with extents on this mdisk, the command will start to fail. Wait until the migration is complete, and re-run this command as necessary.
  • The "-nohdr" option on the lsmdiskextent prevents the headers from being printed which would confuse things. (but not in a bad way, it would just error out with this error:
    CMMVC5716E Non-numeric data was entered for a numeric field ([number_of_extents]). Enter a numeric value.

Show status of extents migration (above):

function progress { echo "Waiting: $(svcinfo lsmigrate | while read x y; do [[ $x = progress ]] && p=$y; [[ $x = migrate_vdisk_index ]] && vdisk=$y; if [[ $x = number_extents ]]; then if [[ $p = 0 ]]; then echo -n "$vdisk "; else printf "Vdisk %3d %3d%% of %4d extents\n" "$vdisk" "${p}" "$y" >&2; fi; fi; done)"; }

Show the space used by vdisks for a series of hosts

hosts="Integra Miata Impreza Element Fit Saab"; eval echo $(( $(for x in $hosts; do svcinfo lshostvdiskmap -nohdr -delim : $x | while IFS=: read a b c d e f; do svcinfo lsvdisk $e | while read z y; do [[ $z = real* ]] && echo -n "${y%.??GB}+"; done; done; done; echo "0" ) ))
The hosts are defined in the space-delimited list in variable "hosts"; they must match the SVC host object name exactly, or can be the host object IDs.
Note: makes the assumption that all of the vdisks are listed in GB. Will not work with vdisks that show size in MB or TB. could be adjusted to work with this situation, though.

Show useful information about an mdisk

function mdinfo { typeset md=$1; if [[ -z "$md" ]] || ! lsmdisk="$(svcinfo lsmdisk $md)"; then echo "Error - bad mdisk..."; return 1; fi; eval $(echo "$lsmdisk" | while read a b; do [[ $a == id ]] && echo "mdid=$b"; [[ $a == name ]] && echo "mdname=$b"; [[ $a == capacity ]] && echo "mdcap=$b"; [[ $a == UID ]] && echo "uid=${b:28:4}"; done ); mdiskusedexts=$(($(svcinfo lsmdiskextent -nohdr $md | while read a b; do echo -n "$b+"; done)0)); mdiskfreeexts=$(svcinfo lsfreeextents $md | while read a b; do [[ $a == number_of_extents ]] && echo $b; done); mdisksize=$((mdiskusedexts+mdiskfreeexts)); printf "%2s %-16s %5d/%5dexts (%-8s) %3d%%used %5d free - ID %4s\n" "$md" "$mdname" "$mdiskusedexts" "$mdisksize" "$mdcap" "$((mdiskusedexts*100/mdisksize))" "$mdiskfreeexts" "$uid"; }
Example output:
DS4800-Array-9     698/ 4359exts (1089.9GB) 16%used  3661 free

List the quorum MDisks

svcinfo lsmdisk -nohdr | while read id name rest; do svcinfo lsmdisk $id | while read key value; do if [ "$key" == "quorum_index" ]; then if [ "$value" != "" ]; then echo "MDisk $id ($name) is quorum disk $value"; fi; fi; done; done

List the VDisks which are not mapped to a host

function lsfreevdisk { svcinfo lsvdisk -nohdr | while read id name rest;do if [[ -z $(svcinfo lsvdiskhostmap -nohdr $id) ]] ; then echo "VDisk '$name' is not mapped to a host"; fi; done; }

Show SCSI ID and last four digits of LUN ID for DS4800 disk:

svcinfo lsmdisk | while read id mdisk stat manage mdg mdiskgrp size scsi controller diskid; do [[ $controller != DS4800 ]] && continue; echo "$id $mdisk ${scsi:14:2} ${diskid:28:4}"; done
  • Shows mdisk ID, mdisk name, SCSI ID (two digits), and LUN ID (last four digits)

Generate a CSV of mDisk/vDisk extent distribution

 echo vDisk,mDisk,Controller,mDisk Group,Extents;

 vdiskIds=(`svcinfo lsvdisk -nohdr | while read id rest; do echo -n "$id "; done`)
 vdiskNames=(`svcinfo lsvdisk -nohdr | while read id name rest; do echo -n "$name "; done`)
 vdiskNameMap=()
 for (( i = 0 ; i < ${#vdiskNames
]} ; i++ ))
 do
 vdiskNameMap[${vdiskIds[$i]}]=${vdiskNames[$i]}
 done

 svcinfo lsmdisk -nohdr | while read mdiskId mDiskName status mode mdgId mdgName capacity LUN controllerName UniqueID;
 do
 svcinfo lsmdiskextent -nohdr $mdiskId | while read vdiskId extents;
    do
     echo ${vdiskNameMap[$vdiskId]},$mDiskName,$controllerName,$mdgName,$extents;
    done
 done

Redirect the output of your SSH command (with which you submitted the above) to a CSV file.
You can then open the CSV with Excel and do some Pivot Table magic to get pretty graphs for your management

Storage of deprecated functions

Show status of MDiskGrp Migration: function progress { echo "Waiting: $(svcinfo lsmigrate | while read x y; do [[ $x = progress ]] && p=$y; [[ $x = migrate_source_vdisk_index ]] && vdisk=$y; if [[ $x = migrate_source_vdisk_copy_id ]]; then if [[ $p = 0 ]]; then echo -n "$vdisk "; else printf "Vdisk %3d %3d%%\n" "$vdisk" "${p}" >&2; fi; fi; done)"; }

Handy SVC mini-scripts

In a former life, I was a storage administrator for Thomas Jefferson University Hospital in Center City, Philadelphia. Aside from the wide array of lunch options we had to choose from, another thing I really enjoyed was working with IBM's SVC, or SAN Volume Controller. As a sysadmin, there are few software packages that you truly enjoy working with and you think are really, really good, but the SVC was one of those. One of the reasons was because the command-line interface was just a restricted Bash shell - something I knew very, very well. This allowed me to do pretty amazing things, as the shell is a wonderfully capable programming language. Back then, I had a Wiki that I used as a notepad, and I had several pages on the cool things you could do with SVC on the command line. Here is one of those pages, cut-and-pasted from my internal webserver. Be warned, though - none of this has been updated since about 2007!

Thanks to the awesome Aussie Storage Blog for the inspiration to resurrect these old pages.


    SVC's command line interface (CLI) is a restricted bash shell. You might not be able to cd to other directories or run commands like "grep" or "awk", (see below) but you can still do some really useful stuff using the shell builtins available to you.

    Basic info


    • The "for var in blah blah blah blah; do; done" and "while read; do; done" loops are very powerful tools available to the shell script programmer, and are the backbone of virtually every miniscript I write.
    • SVC's bash is restricted, but otherwise the same as the bash you would find on any linux box. Prior to SVC 4.2, the bash version is 2.05. At 4.2, the bash is updated to 3.1. If you've got a linux system available to play with, you can try out these miniscripts there before you try them on your production SVC. If you want to test these scripts on a different linux system, you might want to see the SVC miniscript testing page.
    • Before you actually run commands, ESPECIALLY on an SVC where you can cause some serious damage to your SAN, prefix the command with an "echo" so you can see the command it would run without actually running the command!

      So, before you run "svctask rmvdisk $x" from inside a loop; first run "echo svctask rmvdisk $x" and verify that it's going to do what you think it's going to do. Much better to see that you messed something up on the screen than let your SVC try to remove every vdisk you've got assigned. Clients will not be happy nor will they be too understanding when you tell them you were using some ultra-fancy "bash miniscripts" to make working on the SVC more efficient.

    Possible gotchas


    Beyond the obvious gotchas of really screwing up the configuration of your SVC if you get your miniscripts wrong, there are a couple little gotchas to be aware of.
    * Variables set inside loops are not available outside of loops.
    found=0
    svcinfo lsvdisk -nohdr | while read line
    do
        [[ $line == *DS4800* ]] && (( ++found ))
    done
    echo "Found $found vdisks on the DS4800"
    This will always report 0 vdisks, even if it does find vdisks, because the loop executes in a subshell, and thus any modification to the variable won't be available to the parent shell. A workaround is to use command substitution and catch the STDOUT of the loop:
    found=$(svcinfo lsvdisk -nohdr | while read x; do [[ $x == *DS4800* ]] && echo -n X; done)
    echo "Found ${#found} vdisks on the DS4800"
    For each disk I find, I echo an "X". At the end of the loop, I've got stored in the variable "found" something which looks like "XXXXX" for five vdisks. Echoing "${#found}" gives me the length of the variable, and thus, the number of vdisks. Painful workaround? Yep.
    This is a bash thing -- don't blame SVC. Korn shell (ksh) works just fine either way, but we're not running on the ksh, are we?

    Multi-line entry


    To make things more readable, you can enter your mini scripts on multiple lines:
    svcinfo lsmdisk -delim : | while read line
    do
        echo "$line"
    done
    (this miniscript doesn't really accomplish anything... just echoes what it reads, as if we ran "svcinfo lsmdisk | cat")
    Bash will convert your entry to a one-liner (separating lines with ";" as appropriate) when it enters it into the history file, so the above will look like this in the history file:
    svcinfo lsmdisk -delim: | while read line; do echo "$line"; done
    Because of this, when I'm writing mini-scripts, I just edit them in the one-line format. Your option.

    Shortening command lines


    if/then with only one command can be simplified:
    if [[ $x = "yes" ]]
    then
        echo "yes"
    fi
    can be simplified to:
    [[ $x = "yes" ]] && echo "yes"
    if/then/else with only one command can be simplified:
    if [[ $x == "yes" ]]
    then
        echo "yes"
    else
        echo "no"
    fi
    can be shortened to:
    [[ $x == "yes" ]] && echo "yes" || echo "no"
    Multiple commands can be run as well:
    if [[ $x == "yes" ]]
    then
        echo "yes"
        runcommand
    fi
    can be shortened to:
    [[ $x == "yes" ]] && echo "yes" && runcommand
    • caveat: The shortened version will not work exactly the same as the long version. In the short version, "runcommand" is only run if the first command, echo "yes" is successful. Odds are good that an echo isn't going to fail, but if "runcommand" was first and it failed, the next command doesn't run. In the "long" if/then format, both commands will be run, even if the first fails.
    • Basically, unless you really understand how the "&&" and "||" dividers work, stick to single commands in your shortened if/then commands. Anyhow, the if/then syntax for multiple commands (on one line) is still pretty easy:

      if test; then command1; command2; fi

    No "ls"? No problem!


    SVC's restricted bash shell gives you very, VERY few commands. You can still get by without some of them using the bash builtins. Here's how we can simulate the "ls" command:
    echo /dumps/*
    This lists the contents of the /dumps/ directory, but in a pretty ugly format... all the files are listed separated by a space, and wrapped around lines. A little hard to read. Here's how we can have something more like "ls -F" format: ("/" appended to the name of directories)
    for file in /dumps/*
    do
        [[ -d $file ]] && echo "$file/" || echo "$file"
    done
    I don't know of any bash builtins that could give us the equivalent output of "ls -l", though. No way to get file size, permissions, or ownership. You can check if your own permissions as related to the file (can I read, write, execute) but can't see who owns it. That said, if the file is there, as admin you will have permissions to access it

    No "grep"? No worries!


    Wouldn't it be nice to "svcinfo lsvdisk | grep MyDisk"? Here's how: (basic form)
    svcinfo lsvdisk -nohdr -delim : | while read line
    do
        [[ $line = *match* ]] && echo $line
    done
    • The bash used in SVC is kinda old (version 2.05 on SVC v4.1) which is too bad. If it were a more modern version (v3.x) there are many REALLY powerful pattern matching capabilities available as builtins... including regular expressions!
    • WUHU! SVC 4.2 has upgraded bash to version 3.x! This opens up some REAL possibilities for powerful pattern matching. See the bash doc for more info.

    No "awk"? No concerns!


    Since most SVC output is field specific, you can use modify the "grep" code above bash to split up the stuff that you're reading:
    svcinfo lsmdiskgrp -nohdr -delim : | while IFS=: read id name status nummdisk numvdisk size extsize free
    do
        [[ $name == *4500* ]] &&  echo "MDiskGroup $name (id $id) has $free available of $size"
    done
    Note: Using the ":" delimiter and setting IFS allows us to handle blank fields. F'rinstance, "lsvdisk" often has blanks for FC_id, FC_name, RC_id, and RC_name. (unless you've got Flash Copy on that vdisk) but the vdisk UID might be important to you. If you don't set IFS and leave "delim" unset, there will just be extra blank space, and the vdisk UID will be set to the "FC_id". Setting the IFS to the delimiter makes it set the blank fields to blank. IF you don't have to deal with blank fields (like say lsmdiskgrp) then you can leave the delim unset and just use a read: (you should still turn off the header so you don't process that!)
    svcinfo lsmdiskgrp -nohdr | while read id name status nummdisk numvdisk size extsize free
    do
       ...

    No "sleep"? Now that's a problem.


    If you wanted to run a command repeatedly, separated by a certain time delay, I haven't figured out how to do that yet. The "sleep" command, oddly enough, isn't a bash builtin. (even though it seems like it could be, very easily... and not to harp on ksh93, but it's a builtin there as well, and takes fractional delays, to go with ksh93's capability for floating-point math... oh, and it leaves variables set in loops available to the original shell...)
    One option I considered was checking the first field of /proc/uptime in a loop, and exiting the loop when the value equals (( initialvalue + sleeptime )), but the problem there is that bash will check the contents of the file as often as it can, many times a second, creating a possibly significant load on the system, which isn't a good idea.
    • Interactive workaround for no "sleep" command:
    Instead of having the system delay for a certain period of time, you can have the system wait for your input, using the "read" command to read a line of input. You can then CTRL-C or put in a specific exit string:
    while read keyb
    do
        svc command goes here
        [[ $keyb == x ]] && break
    done
    This will execute the command every time you hit ENTER, and if you type a single "x", it will return you to a command prompt. Don't use "exit" instead of "break" or you'll be logged out when you type "x".

    Building associative arrays


    One of the biggest pains in the butt with bash is the variable scoping. If you want to build an associative array of, say, mDisk Name mapping to its state, you can't just set up a while loop with the output of lsmdisk piped into it. The reason is that the pipe spawns a subshell and the array that you're merrily building belongs to the subshell and so ceases to exist once you exit the loop
    I worked out the following method.
    1. Build an array of your keys, using command substitution
    2. Build an array of your values, using command substitution
    3. Iterate over your values and generate your associative array
    eg
    vdiskIds=(`svcinfo lsvdisk -nohdr | while read id rest; do echo -n "$id "; done`)
    vdiskNames=(`svcinfo lsvdisk -nohdr | while read id name rest; do echo -n "$name "; done`)
    vdiskNameMap=()
    for (( i = 0 ; i < ${#vdiskNames[
    } ; i++ ))do
     vdiskNameMap[${vdiskIds[$i]}]=${vdiskNames[$i]}
    
    done @]
    Because no subshell was spawned for the last loop, vdiskNameMap is avaiable for later. I use this in my extent summary script

    Functions in bash


    If you wanted to use the "grep" code above and actually call it "grep", you could do that by defining a function:
    function grep { while read line; do [[ $line == *$1* ]] && echo "$line"; done }
    • Since you can't edit your .bash_profile or .bashrc, every time you log into the SVC, you'd have to re-enter the function. This could be done via cut & paste, or if you wanted to get fancy, an expect script which logged in, defined your functions, and then gave you interactive control.
    Since we don't have the capability to set aliases in restricted shell, you can use this if you want to get rid of the annoying need to prefix everything with "svcinfo" and "svctask":
    function lsmdiskgrp { svcinfo lsmdiskgrp $*; }
    "lsmdiskgrp" now works just like "svcinfo lsmdiskgrp", taking options (like "-nohdr") and arguments (like the name or id or an mdiskgrp) without having to type "svcinfo" first.
    • bash can be finicky when you define a function and put commands in { } brackets. Usually bash isn't like perl in needing each line terminated with ";", but if you have a simple command like we have here, end the command with a ";" before the closing "}". Watch your whitespace, too.

    A more powerful miniscript


    An example. If you're running extents migrations, the progress information given by "svcinfo lsmigrate" is a little hard to read:
    IBM_2145:TJUH_SVC:admin>svcinfo lsmigrate
    migrate_type MDisk_Extents_Migration
    progress 33
    migrate_vdisk_index 13
    migrate_source_mdisk_index 10
    migrate_target_mdisk_index 1
    number_extents 160
    max_thread_count 1
    migrate_type MDisk_Extents_Migration
    progress 90
    migrate_vdisk_index 12
    migrate_source_mdisk_index 10
    migrate_target_mdisk_index 1
    number_extents 60
    max_thread_count 1
    ...and so forth. That's pretty ugly. Wouldn't it be nice if we could see output in a format like:
    Vdisk  13  52% of  160 extents
    Vdisk   1  10% of  819 extents
    Vdisk  10  37% of   64 extents
    Vdisk   7  22% of   96 extents
    Vdisk   4   0% of  194 extents
    Much nicer! It's actually pretty easy. Here's the command line:
    svcinfo lsmigrate | while read x y; do
        [[ $x = progress ]] && p=$y
        [[ $x = migrate_vdisk_index ]] && vdisk=$y
        [[ $x = number_extents ]] && printf "Vdisk %3d %3d%% of %4d extents\n" "$vdisk" "$p" "$y"
    done
    Normally I'll just create the whole command line with one line and semicolons separating commands, since it's easier to return to that line and edit.
    In this instance, 0% means it hasn't started the migration... it can only run four threads at once, so a vdisk at 0% is waiting. Let's fancy it up some:
    svcinfo lsmigrate | while read x y; do \
        [[ $x = progress ]] && p=$y \
        [[ $x = migrate_vdisk_index ]] && vdisk=$y \
        if [[ $x = number_extents ]]; then \
            if [[ $p = 0 ]]
            then
                 echo "Vdisk $vdisk is waiting..."
            else 
                 printf "Vdisk %3d %3d%% of %4d extents\n" "$vdisk" "${p}" "$y"
            fi; fi
    done
    That produces output like this:
    Vdisk   1  35% of  819 extents
    Vdisk   4  85% of  194 extents
    Vdisk   5  82% of  160 extents
    Vdisk   0   6% of   16 extents
    Vdisk 14 is waiting...
    Vdisk 17 is waiting...
    Vdisk 32 is waiting...
    ...and so forth!
    As you can see, the command lines can get a little complicated, but you can do some REALLY powerful stuff.

    More advanced stuff


    • A progress bar could be simulated like so:
      ->
      bar="########################################"       # That's 40 pound signs
      barlen=${#bar}                                       # Sets "barlen" to 40 - the length of "bar"
      space="                                        "     # 40 spaces
      blocks=$((p*barlen/100))                             # How many blocks for that percentage.
      echo ">${bar:0:$blocks}${space:0:$((barlen-blocks))<"

      For a value of p=70; output would look like this:
      ->>############################ <
      pmwiki isn't showing all the spaces properly here... there would be 28 pound signs followed by 12 spaces, keeping the whole width between the > < characters at 40 characters.

    Bash links


    Wednesday, August 12, 2015

    Adjustments to the Mac OS X Dock

    I don't use the dock in OS X. I don't find it necessary - I can open apps with CMD-space, (using spotlight to search) and I can switch between apps with cmd-tab. So, I auto-hide it, which gives me much more usable space on the screen, but this winds up being an annoyance when I have to click something down near the bottom of the screen, as the dock may pop up in the way of what I'm trying to click. So, after some searching, here's some command line adjustments I made to the dock:

    To increase the delay before the dock hides:

    defaults write com.apple.dock autohide-delay 10 && killall Dock

    This sets the autohide delay to 10 seconds. Initially, I ran into a problem where this seemed to work but only momentarily. After running this, moving to the bottom of the screen resulted in the dock not popping up, but if I move away and back, the Dock pops back up immediately. I've set the delay as high as 1000000 and it acts this way. Further searching revealed that other links had "dock" with a capital D, which makes it not work properly. Make sure you use lower case for "dock".

    Making the dock size as small as possible also helps as it covers less screen if it does pop up. This can be done from System Preferences... Dock.

    To make the dock only be active applications:

    defaults write com.apple.dock static-only -bool TRUE && killall Dock

    (from: http://www.makeuseof.com/tag/customise-mac-os-x-dock-hidden-terminal-commands/)

    This reduces the size further and makes it less intrusive.


    Friday, June 19, 2015

    Initial Hammerhead One review

    Warning! This review is long. You've been warned.

    I finally received my Hammerhead One a week or two ago. This thing's been a long time in the making, starting as a kickstarter (or one of those crowdfunding sites, I'm too lazy to go back and look it up) that I was really excited about. If you're not familiar with a Hammerhead One, it's a bicycle navigation device - here's the manufacturer's own video:




    Hammerhead One demo video

    It's a really, really cool concept, so I was more than happy to back them.

    The process took a while. I knew this from the start, but it certainly seemed to go on quite some time. They were good with updates to the process, but I quickly learned that "we think we'll be shipping it by {insert some point in the near future}" statements were not terribly likely. Eventually, I decided "okay, it'll happen at some point, and it'll be a pleasant surprise when it does". Finally, about a month ago I received an update message that said they'd be shipping out soon. It showed up on my porch one day, and I was pretty stoked!


    Initial thoughts


    • It's a clean design
      The final shipped design is awfully close to the initial designs, and is really clean. Non-cycling friends can (and have) made jokes about the new sex toy, but aside from that it's a pretty cool looking gadget.

    • No documentation at all in the box?
      The box looks pretty good, but one thing I noticed right away is there's no documentation either in the box or on it. If you're going to ship a device with no form of instructions included, you'd better make damn sure it's super intuitive to use and has no gotchas. Turns out, this isn't the case.

      There was, however, a nice thank-you card which lists the names of all the initial supporters. Yep, I'm on there, along with two friends that I know supported it. Cool.

      The only instructions that I received with the unit came in the shipping email:

      "Once you have unboxed your Hammerhead we suggest you connect it to the app and update the Firmware immediately: This video will show you how to update the firmware, and here is an initial turn instruction guide."

      As this was the shipping email, I initially missed it, and as we'll see, that turns out to be not quite enough.

    • Reusing the Garmin mount is a clever approach
      The mount on the Hammerhead uses a standard Garmin bike computer half-turn mount. It's simple, it works, and opens up the ability to use a bunch of other mounts.

    • Sparse documentation online
      After some initial setup issues, I hit the "Help" link in the app which leads to their online FAQ. They've bolstered it quite a bit since I got mine, but at the time, it was really sparse. The only other thing I managed to find were the videos which we've already discussed.

    • Setup video says iOS, no separate video for Android
      Although the app looks almost exactly the same between the two platforms, the setup video that they linked to in the shipping email clearly says "iOS" in the title. So I look for the Android version, and I don't find it. Good thing I know Android and iOS well enough to be able to translate what they're trying to do in the video. It's not hugely different (beyond it actually working in iOS - but more on that in the next section) but it's still different.

    • No GPX import?
      Playing around with the app, it looks like a fairly basic navigation app. I find I get the best results planning rides using a computer and web-based tools instead of an app on the phone - and then being able to import that route into an app on the phone when it comes time to follow it. This doesn't appear to be an option, and it doesn't even look like I can use another tool and then import that path. I understand that this could lead to issues - if I give it a route that's not on its map, how will it know where to turn, and if I go off-route, how will it figure out how to re-route me? It's probably not a trivial solution, but I sure hope that they figure it out.

    Android problems

    I have two phones - my own Android (first gen Moto X) and an iPhone 6 from work. This turns out to be a good thing, because about two weeks into having my Hammerhead, I've still yet to get it working with the Android phone. I've discovered a bunch of fun things, and have been seriously frustrated up to this point, but so far their customer support has been pretty good. Here's a rundown of the problems I've discovered and not yet surmounted:


    • You don't pair it with your phone like a normal Bluetooth device.
      If there were instructions, I might have known that before I started using it. Having missed the one paragraph of instructions in the shipping email, I figured "Okay, it's bluetooth. Let's get it paired up and see what this thing can do." So, I went to the phone's Bluetooth setup menu, just like I've done for all the other Bluetooth things that I have, and tried to set it up. This failed with an "invalid PIN" error. I checked the instructions I received for what to do in that situation and oh wait - I didn't get any. Hah.

      Turns out you have to do the pairing in the app. Why? Fucked if I know. I've got theories.

    • If you manage to lock it up, the only way to reset it is leave it alone and let the battery run out.
      In the process of trying to figure out how to get it to talk to the phone (remembering that I missed the one paragraph of instructions with links to videos for a mobile OS that I was not using) I managed to lock it up. The device has one button which is ringed by light when it's on, and at some point this went solid greenish-blue and the device stopped responding at all. The phone wasn't able to see it anymore - it was locked up. With no troubleshooting instructions and nothing of use I could find on the FAQ (which was much less populated than it is now and had almost nothing about connectivity issues) I was left to my own troubleshooting steps. I'm a computer guy, I've got decent troubleshooting skills. The first thing I tried was to press and hold the button, as many electronic devices honor that as a "turn off regardless" indicator. Holding the button down for well over a minute made me think that wasn't the case. Tapping the button did nothing. There are screws on the back of the unit and I considered opening it up - they're just T-6 Torx, which I have - but at this point the thing is brand new and I decided I didn't really want to do that.

      Ultimately, I left it alone and let the battery die off. Once that happened, I could start trying to use it again, until I locked it up again and had to let it die all over again. Speaking with support, I was told that I could reset the unit by "Plug in the USB cable and hold the main button for 6 seconds. Then remove the USB cable and hold the main button for 3 seconds." This procedure kinda sucks since I need to have a USB cable on hand and leaves a bunch of questions unanswered:
      • Do I keep holding the button when I unplug the USB or do I let it go?
      • Does the USB cable have to be plugged into the wall, or would a cable by itself do the trick?
      • Most importantly, why didn't it work with any variation of the procedure that I could think of?

    • Removing the charging port cover isn't intuitive or documented
      The charging port cover is easy to find, at the bottom of the unit on the back. Getting it off is another matter. It looks like it should just slide off away from the bottom of the unit, but trying to do that by hand without forcing it wasn't resulting in anything. There's a tab on the back of the device that looked like maybe it needed to be depressed in order to let the cover slide off, and I tried that. Turns out that's exactly what you don't want to do as it keeps the cover in place and pressing on it increases the lock, not decreases it. I see the FAQ entry has been updated to state this now - but that wasn't there the other day when I tried it. Also, the FAQ entry had one single photo with an arrow on it pointing to the charging port. The problem here is that the photo looks like a quick shot with a cell phone camera in crappy lighting so the arrow points to an area on the phone you could have found on your own (I did) and the relevant point on the unit is a featureless blob of black, so you really don't get any information from the photo which isn't already obvious. See what I mean?
      Crappy white balance and exposure make this photo useless.

      With a T-shirt providing a dark background so it doesn't throw off the auto exposure and sunlight from the window by my desk, I managed to get this photo:
      Less than a minute of work and a much more useful photo.
      I didn't include the cover because I took that off and just left it off.

      I eventually managed to get the cover off by sticking the point of a screwdriver in the space around the tab that holds it in place and pushing it away in the direction I guessed was right.

    • The charging light is hard to see
      When I first let it die off because it was locked up, and then plugged it in, no lights came on. Considering the thing is covered with LEDs, I was kinda surprised about that. No form of indication if it's charging or done? Fail.

      Well, turns out I was wrong. There's a small green LED on the side of the unit which shows when it's plugged in. I had initially missed it. Let's see why:
      The charging light is on. Can you see it?
      See it now? The viewing angle is quite shallow.
      I also don't think the light changes based on the charge level. It's just an indication that it's plugged in. So, is it fully charged? Well, the app can tell you that, but a simple flashing during charge, solid when fully charged would remove the need for the app.

    • The firmware process isn't well documented or intuitive
      The only instructions for upgrading the firmware are on the iOS video. It just shows the procedure happening once and leaves out pretty important details. I managed to assemble the proper procedure from the video, the light sequence demo video, and other information I managed to find online.

    • Lack of information on firmware versions
      Since the only place you can update the firmware is through the phone app, you never have to manually download it, and that's nice. However, there's no way to see what the current released version of firmware might be - or changelogs, or anything like that. As a computer guy, I'm used to things like this. For the general user, not a huge problem, but since they recently uploaded a video mentioning firmware 1.5, and I'm at 1.3.x, I have to assume that it hasn't been released because "Update firmware" is greyed out in the app. Or maybe something's broken. I can't tell.

    • The Android app won't actually talk to my phone
      After finally managing to update the firmware on my device, I tried to use it with my Android phone. I can create route instructions, but when I try to ride it, I get the following error:
      "wait for some time." ?? Dafug?
      Despite "waiting for some time" - it never sees my Hammerhead.

    • The Android app crashes repeatedly
      Related to the above point, when I click "Skip" on the "Connecting to Hammerhead" prompt above, the app closes and I get this:
      Yay, crashed!


    • All of this works on my iPhone
      I guess we know where they spent all their development effort and QA testing.

    Test drive on my commute to work

    So yes, I know that my first test with this thing should be on a bike, not driving in a car, but time constraints combined with curiosity/impatience led my first test to be my driving commute to work. It's actually not awful because I know the roads between home and work pretty well so should be able to follow pretty close to the route the app suggests, and it'd be interesting to see how it handles things. The key, however, with anything that gives you directions is you should get familiar with it on roads and paths that you _know_ so you learn its quirks - you don't want to find these out blindly following it where you have no idea where you _should_ be going.

    For this test,  I had the hammerhead propped up in front of my speedometer so I could see the lights out of my peripheral vision, and the iPhone was in my phone holder so I could see the route it had planned out for me.

    • It really prefers bike paths to streets
      This isn't too surprising, since it's designed first and foremost for giving directions to road bikers. Still, the roads around my house are quite nice and cyclist friendly. Here's the way it suggested I start my ride:
      It's pretty, but a circuitous 2.8 miles.

      Perfectly valid route that's a full mile shorter.
      While the route it suggested is pretty and almost completely avoids any roads, the way I usually go is perfectly fine for cyclists and takes 1 mile off the ride before I've even gotten to Route 202.

    • It prefers a shared-use path to a marked bike lane.
      Similar to above, Route 202 has a shared-use path that runs along side the road, separated by a fence. It's a nice way to go - but 202 also has marked bike paths on the road. If you chose to do that (admittedly, few cyclists do, but I have) it will confuse the app, frequently telling you to turn around and head back when the shared use path goes away from the road too far.
      Route 202 has bike lanes and a separate shared-use path. gmaps

      If you don't already know where you're going, the indication for a U-turn while you're heading the right way on a marked bike lane could be confusing as hell.

      If you do already know where you're going, then you don't really need a blinky thing on your handlebars telling you where to go, do you?

    • The routing was pretty good for a cyclist in the area
      Ignoring wanting to use bike paths over roads and insisting on a U-turn when you don't follow it exactly, the routing was pretty good. There are some tricky areas for bikes on my commute, and it managed to avoid those pretty effectively.

    • It tried to direct me over a bridge that has been closed for a month or two
      I use the Waze app for driving. It's a community-supported app that allows you to report problems on the road, including road closures. One of the routes I take to work has a bridge which is currently closed for repairs. I was able to tag the bridge as unavailable in the Waze app so that other folks knew to not go that way.

      Unfortunately, Hammerhead's map doesn't know this, and tried to direct me over the same bridge. When I took my usual detour around the closed bridge, the Hammerhead was diligently instructing me to make a U-turn for well over a half mile before it finally figured out the way I was actually going.

    • The amount of pre-turn warning that you get seems to be based on distance from the turn
      Okay, this is kinda a bullshit observation since I was in a car, going far faster than the app was designed for, but the turn notifications I was getting happened *very* close to the turn. At bike speed, that'd be just fine, but adjusting the amount of warning based on the speed doesn't seem like it'd be terribly difficult. More of an observation than a real issue.

    • Several indicated turns that weren't actual turns.
      Driving down a section of road where I had well over a mile to the next turn, it kept indicating left turns. Sometimes those turns seemed to be nothing more than a bend in the road. Sometimes it indicated a left turn on a straight stretch of road where there was nothing beyond a driveway on the left. If I didn't know where I was going and didn't have the map up, that would have been confusing as HELL. Problem is, not knowing where I'm going and not having a map up is exactly the targeted use case for this device.

      I should note that these were indicated left turns - not the slight turns shown in the video. I did see one slight right on my route, which was accurate.

    Conclusion - so far

    I still think the Hammerhead is a great idea and a slick design. It shows real promise. However, I can't help but wonder what the hell they did with 20 months of development time. I can't imagine they offered these to too many folks for beta testing... or is that us, the initial backers? I can't imagine how they managed to go 20 months of development without writing any type of useful doc, or taking more than a single crappy cell phone camera when a simple lightbox setup with a good SLR would have been so much more effective. It's just been a really frustrating first experience with the device.

    Given some time, I think that it still shows huge promise, and I'm not giving up on it yet, but I certainly don't think it's ready for the big time, and I sure won't be using it alone to figure out how to get somewhere. It needs a bunch of work before I'll be ready to trust it that far.

    Wednesday, June 3, 2015

    VirtualBox autostart on boot

    I run a number of VMs inside VirtualBox on a server - while I've got VMware, I'm more familiar with VirtualBox from working with it for Windows VMs on my mac, as well as development using Vagrant and so forth... so I use VirtualBox for some small test systems. Also, I'm told VMware has specific hardware requirements, and although I've got a stack of enterprise-level servers, they're noisy and generate a surprising amount of heat. In order to avoid having to set up the air conditioning in my computer room, I run my VMs on a decent desktop-class system which is quiet and generates much less heat. Decent trade-off.

    After rebooting I discovered my VMs weren't running, so I took a look online to figure out how to do this, and many places pointed to the same blog post:

    http://lifeofageekadmin.com/how-to-set-your-virtualbox-vm-to-automatically-startup/

    While the procedure worked, it wasn't perfect, so I'm posting my modified version of the procedure here, mostly for my own reference. (run these commands as the user who owns the VBox VMs which will be autostarted.)
    1. Create the file /etc/default/virtualbox with the following contents:

      sudo bash -c 'cat << EOF > /etc/default/virtualbox
      # virtualbox defaults file
      VBOXAUTOSTART_DB=/etc/vbox
      VBOXAUTOSTART_CONFIG=/etc/vbox/autostart.cfg
      EOF'

      *NOTE:
      Don't use vbox.cfg as found in the above link. Apparently some VirtualBox scripts look for that file for other purposes.

    2. Make sure your user is a member of the vboxusers group:

      if ! groups | grep -w nobody; then sudo usermod -aG nobody $LOGNAME; echo Log out; fi


      If you see "Log out" as the output of the above command, log out and back in so your group membership is updated. If you're okay, you should see the output of the groups command.

    3. Create the /etc/vbox directory and the autostart.cfg file:

      sudo mkdir -p -m 1775 /etc/vbox
      sudo chgrp vboxusers /etc/vbox
      sudo bash -c "cat << EOF > /etc/vbox/autostart.cfg
      # Default policy is to deny starting a VM, the other option is "allow".
      default_policy = deny
      # Create an entry for each user allowed to run autostart
      $LOGNAME = {
        allow = true
      }
      EOF"


      You set the sticky bit (the "1" in the "1775" to keep users in the vboxusers group from deleting the files aside from their own. This is how the /tmp directory works - 1777 and users can only delete their own files.

    4. Set the autostart property via VBoxManage

      vboxmanage setproperty autostartdbpath /etc/vbox


    5. Modify the appropriate VM with autostart enabled:

      vboxmanage modifyvm Graphite --autostart-enabled=on


    6. Restart the vboxautostart-service"

      systemctl restart vboxautostart-service

    7. Restart your system and confirm that the VMs have started:

      vboxmanage list runningvms

    Thursday, April 23, 2015

    Logging FreeNAS performance data into Graphite

    Update 12/23/2015 - I now have an updated post which supercedes this post.

    Update 12/2/2015 - This information is dated, and there's a really good way to handle FreeNAS logging to Graphite with FreeNAS 9.3 that I need to document. I'll update this post with a link once I get that post done. In the meantime, this is just a placeholder.

    FreeNAS is a great NAS appliance that I have been known to install just about anywhere I can. One of the things that makes it so cool is the native support for RRD graphs which you can view in the "Reporting" tab. What would be cooler, though, is if it could log its data to the seriously awesome metrics collection software Graphite. I've been working on getting Graphite installed at work, and have always wanted metrics collection at my house (because: nerd) and so once I got a Graphite server up and running, one of the first things I did was modify my FreeNAS system to point to the Graphite server.

    Here are the steps I followed on FreeNAS 9.2.1.8.  I'm overdue for FreeNAS 9.3 and once I've done the upgrade, I'll update these instructions as necessary.


    1. Install a Graphite server.  Four little words which sound so easy, but mask the thrill and heartbreak that can come with trying to accomplish this task. I tried several guides and was a little daunted when I saw most of them mentioning how much of a pain in the ass installing Graphite can be, but then I managed to find this nice, simple guide that used the EPEL packages on a CentOS box. Following those instructions, I managed to get two CentOS 6 boxes up and running in pretty short order, and then with some slight modifications, I set up a CentOS 7 graphite server at home.
    2. Make sure port 2003 on your Graphite box is visible to your FreeNAS box. This usually involves opening some firewall rules.
    3. SSH into your FreeNAS box. (If you don't know what this means, you probably never got to this step, as "Install a Graphite server" would have entirely broken your will to live.) You will also need to log into your FreeNAS box as either root, or a user that has sudo permission.
    4. Edit the collectd config file:
      1. sudo vi /etc/local/collectd.conf
      2. At the top of the file, change "Hostname" from "localhost" to your hostname for the NAS. Otherwise, your NAS will report to the Graphite host as "localhost", and that's less than useful.

        Hostname "nastard"
        ...
      3. There is a block of lines all starting with "LoadPlugin". At the bottom of this block, add "LoadPlugin write_graphite":

        ...
        LoadPlugin processes
        LoadPlugin rrdtool
        LoadPlugin swap
        LoadPlugin uptime
        LoadPlugin syslog
        LoadPlugin write_graphite

        ...
      4. At the bottom of the file, add the following block, substituting the hostname for your graphite hostname:

        ...

         
            Host "graphite.example.net"
            Port "2003"
            Protocol "tcp"
            LogSendErrors true
            Prefix "servers."
            Postfix ""
            StoreRates true
            AlwaysAppendDS false
            EscapeCharacter "_"
         
    5. Change to the directory "/var/db/collectd/rrd" - this is where FreeNAS logs its RRD metrics that is visible in the GUI. If we just restart collectd, it's going to start logging under the hostname (since we changed that above) and that'll break the UI's RRD graphs. While we'll still have the data in graphite, we can have our graphite cake and RRD eat it too by doing the following steps.
    6. Shut down collectd:

      sudo service collectd stop
    7. Move the "localhost" directory (under /var/db/collectd/rrd) to whatever you set the hostname to in the collectd.conf above:

      sudo mv localhost nastard
    8. Symlink the directory back to "localhost":

      sudo ln -s nastard localhost
    9. Restart collectd:

      sudo service collectd start
    10. That's it! At this point, you can reload the FreeNAS GUI and see that you still have your RRD data, but more importantly, if you go to your Graphite GUI, you'll see that you should now be getting metrics.

    Protips:

    • Collectd writes data every 10 seconds by default. If you write all your collectd data with the "servers." prefix as I've shown above, you can make sure your whisper files are configured for this interval with the following block in your /etc/carbon/storage-schemas.conf:

      [collectd]
      pattern = ^servers\.
      retentions = 10s:90d,1m:1y

      This will retain your full 10s metrics for a month (30d), 1 minute interval metrics for a year. That config results in each whisper file being 15MB, and with my NAS config (with 6 running jails) I have 220 whisper files for a total disk space of 1.2G. Considering disk space is pretty cheap, you could easily adjust these numbers up to retain more data for longer.  You should also read up on the graphite aggregator which controls how the data is parsed down when it's saved at lesser intervals.

      Thanks to Ben K for pointing out that more than one or two aggregations will greatly increase the amount of disk access. Initially I had a four stage aggregation, but that would require a crapload of access happening with each write. Since Graphite is very IO intensive to begin with, that's not a good idea.