<?xml version='1.0' encoding='UTF-8'?><rss xmlns:atom='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0' version='2.0'><channel><atom:id>tag:blogger.com,1999:blog-2932097807811939207</atom:id><lastBuildDate>Sun, 29 Apr 2012 16:13:53 +0000</lastBuildDate><category>webservice</category><category>rest</category><category>xml</category><category>bgp</category><category>networktopology</category><category>activemq</category><category>postgresql</category><category>javascript</category><category>sql</category><category>java</category><category>soa</category><category>google visualization api</category><category>rex</category><category>uw_tech_tools</category><category>pchart</category><category>http</category><category>meerkat</category><category>kpi</category><category>sqlx</category><title>UW Network Tools</title><description>A blog by the members of the University of Washington's Network Management Tools Team--a team of software engineers working in UW-IT's Application Infrastructure Engineering group.</description><link>http://tools.cac.washington.edu/</link><managingEditor>noreply@blogger.com (dharmabruce)</managingEditor><generator>Blogger</generator><openSearch:totalResults>15</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-6099755981184695493</guid><pubDate>Fri, 10 Feb 2012 22:46:00 +0000</pubDate><atom:updated>2012-02-10T14:46:35.480-08:00</atom:updated><title>strace and tcpdump: or  "a good time with dhcpd and iptables"</title><description>&lt;p&gt;After an upgrade to our DHCP servers resulted in a failure to hand out leases under peak load, we enhanced our DHCP load testing client and started narrowing down the problem. &lt;/p&gt; &lt;p&gt;Testing soon sorted out the performance issue (while the new disks were much faster for reads and writes, but the old disk controller had a write cache. The &amp;#8216;fsync() after every lease&amp;#8217; behavior of dhcpd meant that we were getting killed writing the leases file).&lt;/p&gt; &lt;p&gt;I wanted to quantify how many DHCP leases and requests we could serve once we&amp;#8217;d resolved that and I noticed one odd little problem under load. If we set our lease time down to 60 seconds and had more than 600 clients, the dhcpd.log would start reporting an error when sending certain DHCP ACK messages:&lt;/p&gt; &lt;p&gt;&lt;code&gt;send_packet: Operation not permitted&lt;/code&gt;&lt;/p&gt; &lt;p&gt;At first I thought it was happening when the client entered the &amp;#8216;rebinding&amp;#8217; phase (i.e. after the unicast request for a lease renewal had failed, timed out and then switched back to broadcast) because it happened after the initial leases were offered and after some leases had successfully renewed. But closer investigation showed something more interesting.&lt;/p&gt; &lt;p&gt;The first thing to know is that we run our DHCP servers in failover mode and they are located on different subnets. None of the clients would be local in production so we test with a similar configuration. The host the client software runs on is located on a different network from both DHCP servers.&lt;/p&gt; &lt;p&gt;The test client itself is written in java and creates multiple threads, doling out tens or hundreds of thousands of clients requesting IP addresses in batches of 300 at a time. It tracks the clients, following up with a unicast RENEW. If the RENEW times out, it has the client thread go through the broadcast REBINDING process. If that fails, it expires the lease and logs the problem. When the test client is acting like a relay it sets fields in the DHCP packet just like a router with a &amp;#8216;helper-address&amp;#8217; would. &lt;/p&gt; &lt;p&gt;We didn&amp;#8217;t want the network engineers to have to change all the routers between the client and the two pre-production servers by adding routes for a large block of test addresses. &lt;/p&gt; &lt;p&gt;In the past we used iptables on the test DHCP servers to &amp;#8220;DNAT&amp;#8221; (destination NAT) all traffic to our test network to port 68 on the DHCP test client server with this command:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;iptables -t nat -A OUTPUT -d 10.10.0.0/255.255.0.0 -p udp -j DNAT --to-destination 172.16.8.3:68&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;resulting in:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Chain PREROUTING (policy ACCEPT)&lt;br /&gt;target     prot opt source               destination         &lt;br /&gt;&lt;br /&gt;Chain POSTROUTING (policy ACCEPT)&lt;br /&gt;target     prot opt source               destination         &lt;br /&gt;&lt;br /&gt;Chain OUTPUT (policy ACCEPT)&lt;br /&gt;target     prot opt source               destination         &lt;br /&gt;DNAT       udp  --  anywhere             10.10.0.0/16        to:172.16.8.3:68 &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Under low load, everything worked fine. Even under high load (once I&amp;#8217;d worked around the issue with the fysnc() performance) I saw significantly higher DHCP ACK counts on the new servers. But I saw the send_packet complaints above intermittently appear in the log file and I saw clients go into rebinding even under relatively low load conditions.&lt;/p&gt; &lt;p&gt;A little searching turn up the claim that &amp;#8216;Operation not permitted&amp;#8217; was related to iptables, but careful review of the iptables entries on the DHCP servers and examination of the connection tracking table limits didn&amp;#8217;t show an obvious problem. The fact that most of the time traffic got through suggested the problem wasn&amp;#8217;t a bad rule. And all the connection tracking limits were greater than 600 while the problem could be demonstrated with as few as 600 client threads.&lt;/p&gt; &lt;p&gt;Using the &lt;strong&gt;dhcpd source&lt;/strong&gt;, I was able to add some debugging to narrow things down a bit more:&lt;/p&gt; &lt;ul&gt;&lt;li&gt;the problem only happened when a unicast packet was received from a client (but didn&amp;#8217;t happen for all unicast packets)&lt;/li&gt;&lt;li&gt;the same functions and system calls were always being used to generate the responses whether there were issues or not&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Using &lt;strong&gt;tcpdump&lt;/strong&gt; on the DHCP servers to inspect the traffic I found a few more interesting things:&lt;/p&gt; &lt;ul&gt;&lt;li&gt;even though a DHCP ACK was logged in the dhcpd.log file, the corresponding packet was not seen by tcpdump (so the &amp;#8220;send_packet:&amp;#8221; complaint really did indicate a failure)&lt;/li&gt;&lt;li&gt;the complaint never happened until after the first 254 addresses were handed out. It always seemed to start when the client with 10.10.2.0 came back to renew.&lt;/li&gt;&lt;li&gt;the unicast responses from the DHCP server weren&amp;#8217;t coming from port 67, but were going out from a variety of other low-numbered ports&amp;#8230;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Using &lt;strong&gt;strace&lt;/strong&gt; proved most informative of all: &lt;/p&gt; &lt;ul&gt;&lt;li&gt;the dhcpd sendto() call WAS sourcing traffic from port 67 despite what we saw with tcpdump&lt;/li&gt;&lt;li&gt;EPERM (the error code that corresponds to the message &amp;#8216;Operation not permitted&amp;#8217;) consistently happened at 10.10.2.0 &amp;#8211; until I changed the range in the dhcpd.conf file to start offering addresses from a different range, after which the EPERM error started happening at 10.10.2.19.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;So, back to iptables. Although we thought the DHCP traffic should not be subject to connection tracking due to the following table entries:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Chain PREROUTING (policy ACCEPT)&lt;br /&gt;target     prot opt source               destination         &lt;br /&gt;net_notrk  all  --  anywhere             anywhere            &lt;br /&gt;&lt;br /&gt;Chain OUTPUT (policy ACCEPT)&lt;br /&gt;target     prot opt source               destination         &lt;br /&gt;&lt;br /&gt;Chain net_notrk (1 references)&lt;br /&gt;target     prot opt source               destination         &lt;br /&gt;NOTRACK    udp  --  anywhere             anywhere            udp dpt:domain &lt;br /&gt;NOTRACK    udp  --  anywhere             anywhere            multiport dports bootp,bootpc  &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;I was seeing entries in the connection tracking table:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;ipv4     2 udp      17 18 src=192.168.1.12 dst=10.10.0.249 sport=67 dport=68 &lt;br /&gt;          [UNREPLIED] src=172.16.8.3 dst=192.168.1.12 sport=68 dport=84 mark=0 secmark=0 use=2&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;A couple of things jump out:&lt;/p&gt; &lt;ul&gt;&lt;li&gt;as mentioned, I&amp;#8217;m seeing connection tracking entries, despite the configuration above.&lt;/li&gt;&lt;li&gt;the &lt;code&gt;dport=84&lt;/code&gt; in the second line indicates that iptables is choosing to rewrite the source port when it sends out the traffic.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;The first thing to realize is that the configuration above won&amp;#8217;t affect packets sent out from the DHCP servers (note the empty OUTPUT clause). &lt;/p&gt; &lt;p&gt;Secondly, the &lt;code&gt;dport=84&lt;/code&gt; gives a big hint about the cause of the &amp;#8220;EPERM&amp;#8221; problem. &lt;/p&gt; &lt;p&gt;Because the outbound traffic is all destined for the same host and port, the iptables DNAT rule creates an entry in the /proc/net/nf_conntrack table to show how to map any responses. Not that there will be any for this application, of course, but the iptables rules don&amp;#8217;t know that.&lt;/p&gt; &lt;p&gt;Well, okay. So why were packets dropping? We weren&amp;#8217;t hitting connection table limits (the table never got above more than 500 or so entries). &lt;/p&gt; &lt;p&gt;Apparently, because the packets were sourced from port 67 (a reserved port), the implicit source mapping mapped the ports to a new unused port &amp;#8211; but only used ports between 1 and 512. For more details see &lt;a href="http://www.netfilter.org/documentation/HOWTO/NAT-HOWTO-6.html"&gt;Implicit Source Mapping&lt;/a&gt;&lt;/p&gt; &lt;p&gt;So, how do I work around this issue? While it&amp;#8217;s reassuring to know that this would not happen in production, I did want to finish my load testing. I thought perhaps we could DNAT w/o connection tracking, so we went ahead and fixed our connection tracking bypass rule:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;Chain host_notrk (1 references)&lt;br /&gt;target     prot opt source               destination         &lt;br /&gt;NOTRACK    udp  --  anywhere             anywhere            multiport dports bootp,bootpc &lt;br /&gt;NOTRACK    icmp --  anywhere             anywhere            icmp echo-request &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;I anticipate using these settings in production, bypassing connection tracking for all DHCP traffic and for the &amp;#8220;ping&amp;#8221; that the DHCP server does to avoid assigning an address that is being squatted on by another client.&lt;/p&gt; &lt;p&gt;Result? DNAT stopped working entirely. Bypassing connection tracking appears to bypass DNAT, too. &lt;/p&gt; &lt;p&gt;An alternative option is dropping the length of time that UDP entries live in the nf_conntrack table. I don&amp;#8217;t want them there at all, so instructing the system to keep them for 1 second instead of 30 seems to work fine:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;sysctl -w net.netfilter.nf_conntrack_udp_timeout=1&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;I no longer see sendto() EPERM complaints, even at 20,000 clients with a 60 second lease time and we no longer see lease failures (well, with fsync() commented out, that is. I&amp;#8217;m still working on the permanent preferred solution to THAT problem).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-6099755981184695493?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2012/02/strace-and-tcpdump-or-good-time-with.html</link><author>noreply@blogger.com (Heather Sherman)</author><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-7392001200562522522</guid><pubDate>Fri, 03 Jun 2011 22:05:00 +0000</pubDate><atom:updated>2011-06-14T14:54:43.548-07:00</atom:updated><title>mDash, or Systems Monitoring Using jQuery Mobile</title><description>We've written Mobile Dash, or mDash, a mobile web app for monitoring UW-IT&amp;nbsp;system alerts.&lt;br /&gt;&lt;br /&gt;(If you're UW-IT staff, you can check it out here: &lt;a href="https://barista.cac.washington.edu/mdash/"&gt;mDash&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;Some screenshots:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://www.blogger.com/goog_1474719995"&gt;&lt;img border="0" height="640" src="http://1.bp.blogspot.com/-IERjmhKjNvg/Teko7HR72MI/AAAAAAAAAEE/FTZr3ukvyrc/s640/Screen+shot+2011-06-03+at+11.30.55+AM.png" width="339" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;mDash home screen on iPhone 4&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-BvTZejWnJYQ/TekpCBkCZ3I/AAAAAAAAAEI/OUeItG9LYtw/s1600/Screen+shot+2011-06-03+at+10.24.15+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="640" src="http://4.bp.blogspot.com/-BvTZejWnJYQ/TekpCBkCZ3I/AAAAAAAAAEI/OUeItG9LYtw/s640/Screen+shot+2011-06-03+at+10.24.15+AM.png" width="340" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;mDash alert detail page&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;(It's only for UW-IT personnel or we'd have a link for you. If you are UW-IT personnel and want a closer look, feel free to leave us a comment.)&lt;br /&gt;&lt;br /&gt;The interface uses&amp;nbsp;&lt;a href="http://jquerymobile.com/"&gt;jQuery Mobile&lt;/a&gt;.&amp;nbsp;Behind the scenes, mDash uses&amp;nbsp;&lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt;&amp;nbsp;for its Ajax call and DOM manipulation. It also uses&amp;nbsp;&lt;a href="https://github.com/dharmabruce/badgerfishjs"&gt;Badgerfishjs&lt;/a&gt;&amp;nbsp;to transform the alerts from our XML web service into JSON. And some custom CSS adds UW colors to the interface.&amp;nbsp;&lt;/div&gt;&lt;br /&gt;So what is mDash? It's the mobile version Dash. But what's Dash?&lt;br /&gt;&lt;br /&gt;Dash is UW-IT's Java Web Start (Swing) application for monitoring system alerts. One description reads that, "It is intended to be [a] simple and reliable way for system engineers, computer operations, client support groups, and management to view DSUE system status." (DSUE being Distributed Systems and Unix Engineering). By system we mean all sorts of systems. Dash monitors everything from web services used by a handful of developers to student email servers.&lt;br /&gt;&lt;br /&gt;Our team's goal with mDash was to replicate a subset of Dash's functionality in mobile web app form. &amp;nbsp;We wanted to: show what's down (an abbreviation of the hostname), indicate the severity of the problem (the color-coded numbers adopted from the Java app), and allow a person to view a few more essential details via an alert detail page. &lt;br /&gt;&lt;br /&gt;When monitoring critical systems, freshness of information matters. So, on the home page&amp;nbsp;we display the time when alerts were last checked and we provide an unavoidable "Refresh Alerts" button so&amp;nbsp;&lt;a href="http://jnd.org/dn.mss/words_matter_talk_about_people_not_customers_not_consumers_not_users.html"&gt;people&lt;/a&gt;&amp;nbsp;can retrieve the latest alerts when they want. In the future we may refresh the alerts asynchronously, but due to the large amount of alerts that asynchronous process can slow some devices' browsers to a crawl or bring them to a standstill.&lt;br /&gt;&lt;br /&gt;The alert detail page's fields might seem esoteric to people outside DSUE at UW-IT, but these labels are &amp;nbsp;familiar to the smallish set of people who will use this app at the outset. As mDash (and Dash) moves toward wider adoption, we'll need more human-friendly field labels.&lt;br /&gt;&lt;br /&gt;It will sound clichéd, but mDash was fun to make. And jQuery Mobile is what made it fun.&amp;nbsp;We did run into some funky bugs and behaviors along the way, but never insurmountable ones and never ones that the jQuery Mobile community hadn't already solved.&lt;br /&gt;&lt;br /&gt;To say we're looking forward to making another jQuery Mobile web app is understating it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-7392001200562522522?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2011/06/mdash-or-systems-monitoring-using.html</link><author>noreply@blogger.com (jody)</author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-IERjmhKjNvg/Teko7HR72MI/AAAAAAAAAEE/FTZr3ukvyrc/s72-c/Screen+shot+2011-06-03+at+11.30.55+AM.png' height='72' width='72'/><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-966178021308903240</guid><pubDate>Fri, 18 Feb 2011 18:18:00 +0000</pubDate><atom:updated>2011-02-18T10:20:14.872-08:00</atom:updated><title>Following wrong assumptions</title><description>An example of following wrong assumptions when debugging:&lt;br /&gt;&lt;br /&gt;I was porting a bit of hyak (a several hundred node compute cluster)&lt;br /&gt;monitoring from ProxD (an older collector of alerts) to Injector (a newer&lt;br /&gt;generation). This monitor runs a vendor disgnostic utility once a minute&lt;br /&gt;that lists status for every hyak node and issues alerts for any that are&lt;br /&gt;'Down'. It saves status by node, so normally it only generates traffic for&lt;br /&gt;node state changes. The exception is at daemon startup, when (since it&lt;br /&gt;doesn't know whether there might be existing alerts) it issues traffic for&lt;br /&gt;every node (either a Upd if the node is down or a Del otherwise).&lt;br /&gt;&lt;br /&gt;Because determining the state of a node is just looking at the next line&lt;br /&gt;of output, at startup there are going to be 500 odd alerts in a short&lt;br /&gt;period of time, like .02 seconds.&lt;br /&gt;&lt;br /&gt;Usually, there are no nodes down, so if you start the daemon, you won't&lt;br /&gt;notice any dropped alerts (I'm sending these via UDP). Today, though,&lt;br /&gt;there were a few nodes down, and one of them had a high number - n0418 or&lt;br /&gt;something like that. When I fired up my newly ported code, I checked&lt;br /&gt;against the list of active alerts from ProxD - and n0418 was missing.&lt;br /&gt;&lt;br /&gt;I wrote a quicky program to send 500 alerts as fast as possible, and indeed&lt;br /&gt;it start dropping alerts after 150 alerts or so. With ProxD, OTOH, you&lt;br /&gt;could send blocks of 500 over and over w/ no skips. So I started picking&lt;br /&gt;apart the code. The implementations aren't all that different, though, but&lt;br /&gt;they did use different xml parsers and some minor differences. Or could&lt;br /&gt;the read thread be blocking waiting because the write-to-activeMQ queue was&lt;br /&gt;full? Anyway, I spent a couple of hours picking both pieces of code&lt;br /&gt;apart, always looking at millisecond per transaction times - but those&lt;br /&gt;were pretty close - on the order of 3 to 4 millisecs (I dunno if that is a&lt;br /&gt;server limit or not; the client couldn't send faster).&lt;br /&gt;&lt;br /&gt;Oooops. The problem was the udp input buffer size. The new alert&lt;br /&gt;format is a fair amount longer than the old. The proxd one is:&lt;br /&gt;&lt;br /&gt;   &lt;updalert host="foo.u" sev="5" component="myProg" msg="yadda yadda"&gt;&lt;br /&gt;&lt;br /&gt;The new one is:&lt;br /&gt;&lt;br /&gt;   &lt;alert&gt;&lt;br /&gt;     &lt;problemhost&gt;foo.u&lt;/problemhost&gt;&lt;br /&gt;     &lt;component&gt;myProg&lt;/component&gt;&lt;br /&gt;     &lt;severity&gt;5&lt;/severity&gt;&lt;br /&gt;     ...&lt;br /&gt;&lt;br /&gt;and so on. Longer inbound messages fill the buffer sooner - duh. I hadn't&lt;br /&gt;really thought about the performance implications there, and was quite&lt;br /&gt;suprised they made such a difference. I might be a little more terse next&lt;br /&gt;time. Anyway, I increased the buffer size so you can send 2000 odd msgs as&lt;br /&gt;fast as a singlethreaded python prog can send them w/o problems.&lt;br /&gt;&lt;br /&gt;I should have kept an open mind longer - once I focused in on transaction&lt;br /&gt;times, I stopped thinking about buffer sizes; a classic case of narrowing&lt;br /&gt;the suspect list too soon.&lt;br /&gt;&lt;br /&gt;BTW, I went back and tried sending larger numbers of msgs to proxd, and&lt;br /&gt;it started dropping them between 1000 and 2000 msgs, so we had been running&lt;br /&gt;in a sweet spot - no other app sends traffic quite as bursty as this one,&lt;br /&gt;and it was sending a little under the ceiling, so there weren't any&lt;br /&gt;problems. I was also lucky that a high numbered node was down - if it had&lt;br /&gt;been N0099, I never would have noticed.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-966178021308903240?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2011/02/following-wrong-assumptions.html</link><author>noreply@blogger.com (KenM)</author><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-3991010787626648719</guid><pubDate>Fri, 05 Nov 2010 16:29:00 +0000</pubDate><atom:updated>2010-11-05T11:22:32.395-07:00</atom:updated><title>... and ipset comes to the rescue</title><description>We require that hosts located on certain subnets register before they are allowed to use the full capabilities of our campus network.  By default, all hosts on such a network are blocked, until the registration is performed.  While blocked, however, they need to get redirected to the registration page.  For years now, we have used iptables for dynamic dns redirection when performing these host registrations.&lt;br /&gt;&lt;br /&gt;To achieve this, a source NAT mapping is generated to a lying dns server when a host on a network that requires registrations.  This determination is done by a realtime processor script acting on DHCP transitions.  Once a device has been registered the entry is updated to point to the true DNS and and the iptables entry is updated.&lt;br /&gt;&lt;br /&gt;Unfortunately, iptables became a performance bottleneck. The finite time for inserting and deleting a rule for a chain increases as the number of rules in the table increases.  In addition, adding a rule takes two calls to iptables (one to list and make sure the rule does not exist already, a second one to actually add the rule).  This is necessary to prevent duplicated rules in the same chain.&lt;br /&gt;&lt;br /&gt;As the demand for our service increased over the years, the number of rules in iptables increased along with it.  Eventually the realtime processor script became "almost realtime" and then "almost never in time" during peak periods of use.&lt;br /&gt;&lt;br /&gt;Analysis showed that the average time to add or delete an iptables rule was 0.17s (over 100k samples).  At times where the DHCP transition rate could be as high as 1000/min, the iptables updates ran several minutes behind, making the registration process effectively unusable.&lt;br /&gt;&lt;br /&gt;We researched an alternative method of storing the host ip address mappings using ipset  (&lt;a href="http://ipset.netfilter.org/" target="_blank" style="color: rgb(0, 0, 204); "&gt;http://ipset.netfilter.org/&lt;/a&gt;).   Designed to work with iptables, ipset is basically a very efficient bucket for storing network address (ip and port) information.  Time to add and remove addresses is almost constant and much smaller than using iptables at 0.0065s (over 100k samples). This allows more head room for capacity!&lt;br /&gt;&lt;table&gt;&lt;tr&gt;&lt;td&gt;tool&lt;/td&gt;&lt;td&gt;average time&lt;/td&gt;&lt;td&gt;stdev&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;iptables&lt;/td&gt;&lt;td&gt;0.17s&lt;/td&gt;&lt;td&gt;0.12&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ipset tested&lt;/td&gt;&lt;td&gt;0.0065s time&lt;/td&gt;&lt;td&gt;0.034&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;pset in production&lt;/td&gt;&lt;td&gt;0.008s&lt;/td&gt;&lt;td&gt;0.027&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-3991010787626648719?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2010/11/and-ipset-comes-to-rescue.html</link><author>noreply@blogger.com (Nick)</author><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-8583582204418914427</guid><pubDate>Thu, 23 Sep 2010 17:28:00 +0000</pubDate><atom:updated>2010-11-16T13:40:36.084-08:00</atom:updated><title>Jolly times with the postgresql optimizer and explain analyze</title><description>Suddenly yesterday one of my rex functions (REX is our REST/SQL mapper tool) jumped off a cliff, performance-wise.  With a hint from Nick (who will forevermore be known as "the database whisperer") I was able to narrow the performance problem down to this particular query in the function that REX is calling, specifically the family(network)=4 element of the where clause:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;SELECT DISTINCT network, sector.sector::text&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;FROM nfa_k20_network&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;LEFT JOIN info_site ON info_site.siteid = nfa_k20_network.site_id&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;LEFT JOIN dev_location on dev_location.site_id = info_site.id&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;LEFT JOIN dev_classification on dev_classification.device_id =&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;dev_location.device_id&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;LEFT JOIN sector on sector.id = dev_classification.sector&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;WHERE sector.sector is not null and family(network) = 4;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;But I was unable to get the optimizer to generate the same plan on netdbeval/allyourbase as I got on netdbdev.  I'm assuming this is an indication that the data size in one of the tables tipped the query optimizer over into selecting a less ideal strategy, although I'm not clear&lt;br /&gt;on how or why.&lt;br /&gt;&lt;br /&gt;The good strategy uses hashes, the bad one a nested join and a filter.&lt;br /&gt;Here's where things start to run amok:&lt;br /&gt;&lt;br /&gt;Good (netdbdev)  &lt;a href="http://explain.depesz.com/s/ukE"&gt;http://explain.depesz.com/s/ukE&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;Hash Join (cost=3185.37..4996.42 rows=1 width=28) (actual&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;time=953.056..1227.372 rows=7379 loops=1)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt; * Hash Cond: (public.dev_state.device_id = interface.device_id)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Bad (netdbeval) &lt;a href="http://explain.depesz.com/s/TI6"&gt;http://explain.depesz.com/s/TI6&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;Nested Loop (cost=1851.83..8488.08 rows=1 width=34) (actual&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;time=247.812..404814.965 rows=1465 loops=1)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt; * Join Filter: (interface.device_id = public.dev_state.device_id)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Wowsers!   Check out how the actual time compares to the estimated cost!&lt;br /&gt;&lt;br /&gt;Explicitly disabling nested loops in psql before trying this query caused it to go back from taking several minutes to taking 3 seconds, so we clearly want to get postgres to use the first strategy.&lt;br /&gt;&lt;br /&gt;The choice seems to be driven by the estimated row count that &lt;span style="font-family:courier new;"&gt;ifc_ip&lt;/span&gt; will return:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;ripit_2=# explain analyze select count(*) from ifc_ip where family(ip)=4;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;                                              QUERY PLAN&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;------------------------------------------------------------------------------------------------------------&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;Aggregate  (cost=268.85..268.87 rows=1 width=0) (actual&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;time=15.433..15.434 rows=1 loops=1)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;-&gt;  Seq Scan on ifc_ip  (cost=0.00..268.70 rows=61 width=0) (actual&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;time=0.048..9.489 rows=8498 loops=1)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;      Filter: (family(ip) = 4)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;Total runtime: 15.481 ms&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;(4 rows)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Note that the estimated row count is 61, while the actual row count returned is 8498.&lt;br /&gt;&lt;br /&gt;I tinkered with a number of settings (work_mem, effective_cache_size) to see if I could drive the query planner to go back to making better choices.  I also tried analyzing and reindexing the table.  The basic problem seems to be how much filtering the query planner expects the &lt;span style="font-family:courier new;"&gt;"family(ip)=4"&lt;/span&gt; clause to result in and I don't see how the &lt;span style="font-family:courier new;"&gt;pg_statistics&lt;/span&gt; table could capture that without adding an index (heck I don't even see where that mysterious 61 number is coming from).&lt;br /&gt;&lt;br /&gt;A simple and reasonable solution, however, seems to be to rewrite the query as &lt;span style="font-family:courier new;"&gt;"family(ip) != 6"&lt;/span&gt; (the goal of the query is to eliminate any IPv6 addresses from getting returned).&lt;br /&gt;&lt;br /&gt;Doing this results in an estimate that captures something closer to the expected size of the&lt;span style="font-family:courier new;"&gt; ifc_ip&lt;/span&gt; result set (on the high end!) and has the desired effect on the overall query.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;ripit_2=# explain analyze select count(*) from ifc_ip where family(ip)!=6;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;                                               QUERY PLAN&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;---------------------------------------------------------------------------------------------------------------&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;Aggregate  (cost=299.00..299.01 rows=1 width=0) (actual&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;time=15.380..15.381 rows=1 loops=1)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;-&gt;  Seq Scan on ifc_ip  (cost=0.00..268.70 rows=12119 width=0) (actual&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;time=0.049..9.353 rows=8498 loops=1)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;      Filter: (family(ip) &lt;&gt; 6)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;Total runtime: 15.432 ms&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;(4 rows)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The downside, of course, is that I've no idea what might cause the query planner to change its mind in future about this table or perhaps another table lurking in the query.   So my sense is that it might be a more robust solution to add an index, but I'd like to understand better how the returned rows estimation is generated before I go adding more random indexes to tables which, after all, aren't free . . .&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-8583582204418914427?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2010/09/suddenly-yesterday-one-of-my-rex.html</link><author>noreply@blogger.com (Heather Sherman)</author><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-2781397235055827880</guid><pubDate>Fri, 06 Aug 2010 23:15:00 +0000</pubDate><atom:updated>2010-08-11T15:34:31.115-07:00</atom:updated><title>Presenting ... UW Service Tools</title><description>You might have noticed that we've changed the blog's name from UW Network Management Tools to UW Service Tools. The name change reflects &lt;a href="http://uwtechnology-coo.typepad.com/kelli/2010/07/like-the-weather-our-future-is-bright.html"&gt;reorganization within UW Information Technology&lt;/a&gt;. Our group has moved from Network Systems to the Technology Management division's Application Infrastructure Engineering unit--a move we're excited about.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For the most part, what we do on a daily basis remains the same: we'll keep making good software. As for what's new, our focus is widening. We'll be working on automating a wide range of technology services to support UW-IT and UW.&lt;br /&gt;&lt;br /&gt;We'll be posting more news here about our new work soon.&lt;br /&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-2781397235055827880?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2010/08/presenting-uw-service-tools.html</link><author>noreply@blogger.com (jody)</author><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-5262759729904254403</guid><pubDate>Mon, 14 Jun 2010 17:01:00 +0000</pubDate><atom:updated>2010-06-14T11:09:40.583-07:00</atom:updated><category domain='http://www.blogger.com/atom/ns#'>java</category><category domain='http://www.blogger.com/atom/ns#'>activemq</category><category domain='http://www.blogger.com/atom/ns#'>soa</category><category domain='http://www.blogger.com/atom/ns#'>meerkat</category><title>Meerkat Framework: Presentation at  SASAG ...</title><description>Meerkat Framework presentation to the &lt;a href="http://www.sasag.org/"&gt;Seattle Area System Administrators Guild (SASAG)&lt;/a&gt; on June 10, 2010&lt;br /&gt;&lt;div&gt;&lt;a title="View 20100610sasg-pdf on Scribd" href="http://www.scribd.com/doc/33026277/20100610sasg-pdf" style="margin: 12px auto 6px auto; font-family: Helvetica,Arial,Sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 14px; line-height: normal; font-size-adjust: none; font-stretch: normal; -x-system-font: none; display: block; text-decoration: underline;"&gt;20100610sasg-pdf&lt;/a&gt; &lt;object id="doc_138627636295058" name="doc_138627636295058" height="500" width="100%" type="application/x-shockwave-flash" data="http://d1.scribdassets.com/ScribdViewer.swf" style="outline:none;" rel="media:presentation" resource="http://d1.scribdassets.com/ScribdViewer.swf?document_id=33026277&amp;access_key=key-1uu6twwi3j7h29q6ksd5&amp;page=1&amp;viewMode=slideshow" xmlns:media="http://search.yahoo.com/searchmonkey/media/" xmlns:dc="http://purl.org/dc/terms/" &gt; &lt;param name="movie" value="http://d1.scribdassets.com/ScribdViewer.swf"&gt;&lt;param name="wmode" value="opaque"&gt;&lt;param name="bgcolor" value="#ffffff"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowScriptAccess" value="always"&gt;&lt;param name="FlashVars" value="document_id=33026277&amp;access_key=key-1uu6twwi3j7h29q6ksd5&amp;page=1&amp;viewMode=slideshow"&gt;&lt;embed id="doc_138627636295058" name="doc_138627636295058" src="http://d1.scribdassets.com/ScribdViewer.swf?document_id=33026277&amp;access_key=key-1uu6twwi3j7h29q6ksd5&amp;page=1&amp;viewMode=slideshow" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="500" width="100%" wmode="opaque" bgcolor="#ffffff"&gt;&lt;/embed&gt; &lt;/object&gt; &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-5262759729904254403?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2010/06/tools-at-sasag.html</link><author>noreply@blogger.com (Sridhar Komandur, PhD)</author><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-5978792508869416079</guid><pubDate>Fri, 30 Apr 2010 22:04:00 +0000</pubDate><atom:updated>2010-05-19T15:22:15.619-07:00</atom:updated><title>How Kittyfetch Was Named</title><description>&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;In our regular team meeting on Friday April 30, we began discussing the etymology of "kittyfetch", one of our device backup tools. The tool's earlier incarnation was called RANCID (in our wiki it's almost always CAPITALIZED) but we weren't clear how kittyfetch became "kittyfetch." &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;I searched through my chat logs and found that the name dated from November 1, 2007. The transcript follows (emoticons removed):&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;vaughan: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;What do we call the new rancid thingy?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;jtate: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;anything but rancid, please&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;hsherman: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;squirrel.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;or maybe magpie&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;nick?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;wells: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="  font-weight: normal; "&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;ooo, squirrel is a great name!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;hsherman: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;"animals who hoard"&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;vaughan: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;What are those birds that plunder campsites Clarence wonders?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p  style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Helvetica; color:#666666;"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;Anyone?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;benroy: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;camp robbers?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;pack rats &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;hsherman: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;raccoons, except for the not being birds thing&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;jtate: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;type squirrel ten times in a row and then tell me it's a great name&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;vultures&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;amcharg: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;i would not like it if raccoons could fly&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;jtate: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;they do: pigeons&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;hsherman: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;it never needs typing&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;amcharg: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;true and i dislike pigeons&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;hsherman: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;just a cute picture.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;this will actually have the side effect of limiting requests for enhancements to the system.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;wells: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;squirrelsquirrelsquirrelsquirrelsquirrelsquirrelsquirrelsquirrelsquirrelsquirrel&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;are you kinding?  It just &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;*rolls* off the finger tips &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;nickchen&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;why do we need a new name?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;config-backup is good for me&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;or coba&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;cobac&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;jtate: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;copacobana&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;vaughan: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;squirrels&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p  style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Helvetica; color:#666666;"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;yeah, that does type nice.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p  style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Helvetica; color:#666666;"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;gincoba biloba&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;ok, config-backup it is.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;wells: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;LOL&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;nickchen: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;maybe something like, a cat or something&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p  style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Helvetica; color:#666666;"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;kitty&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;kitty go get my backup&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;jtate: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;puma, meerkat, i see a trend&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;cat's don't follow commands well&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;cats&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;nickchen: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="  font-weight: normal; "&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;kitty failed to backup &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;hsherman: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;meerkats aren't cats.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;nickchen: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="  font-weight: normal; "&gt;&lt;span style="text-decoration: underline ; letter-spacing: 0.0px"&gt;&lt;a href="http://upload.wikimedia.org/wikipedia/commons/3/3f/Toilet_Trained_Cat_22_Aug_2005.jpg"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;http://upload.wikimedia.org/wikipedia/commons/3/3f/Toilet_Trained_Cat_22_Aug_2005.jpg&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;hsherman: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;maybe we could continue the non-cat cat theme with "catfish".&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;vaughan: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;LOL re: "kitty go get my backup"&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p  style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Helvetica; color:#666666;"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;[ &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;vaughan&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; is still laughing in his office box. ]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;benroy: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;I think Mr. Bo Jangles is a cat.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span" style="  font-weight: bold; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;wells: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;$ hai kitty does gets backup star-k12&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;- kitty can haz backup star-k12&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;- oh noez, kitty does haz not backup star-k12, sez: "port connection failed"&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'Lucida Grande'"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;- kthnxbye&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;nickchen: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;lol&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"&gt;&lt;span class="Apple-style-span" style="  font-weight: bold; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;amcharg: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;lulz&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;nickchen: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;i shall change all the log messages to lolcats&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;wells: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;we should totally make the "hai kitty" command ... &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;amcharg: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;s/backup/cheezburger/&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt; &lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span style="letter-spacing: 0.0px"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;wells: &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: normal; "&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;haha&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;And so the transcript ends. Somehow, and the history books are unclear, from that day forward the tool was known as kittyfetch. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;To an outsider, the above may not be funny. For us, it's hilarious--and here's why. At a basic level, you need to know about &lt;/span&gt;&lt;/span&gt;&lt;a href="http://en.wikipedia.org/wiki/Lolcat"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;lolcats&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;. Lolcats are an internet meme that may never die. They and their idiolect sneak into our team's everyday communication. How lolcats speak is, well, funny. Very funny. The fact lolcat-speak (&lt;/span&gt;&lt;/span&gt;&lt;a href="http://speaklolcat.com/"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;translators&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt; exist) enters our work at all is evidence of our team's cohesion: we have in-jokes. To put it a basic way, lolcats aside: we laugh together. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;Laughing together and working together can seem to some like two mutually exclusive things, but they're not. Chris Roberts and Wan Yan at the University of Missouri-Columbia found that, "humor among colleagues ... enhances creativity, department cohesiveness and overall performance." Laughter, or humor, in the workplace is a good thing. Our team collaborates well and humor emerges from that collaboration and energizes it. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;Yet, it's become an adage: you can't make fun mandatory. And our fun isn't. So, how did our team come together in such a way that we find kittyfetch a hilarious name? No one knows for sure. But we're glad it happened. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;For more, you can read Roberts and Yan's study: &lt;/span&gt;&lt;/span&gt;&lt;a href="http://www.sciencedaily.com/releases/2007/10/071031130917.htm"&gt;&lt;span class="Apple-style-span"  style="font-family:'lucida grande';"&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;http://www.sciencedaily.com/releases/2007/10/071031130917.htm&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-5978792508869416079?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2010/04/how-kittyfetch-was-named.html</link><author>noreply@blogger.com (jody)</author><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-7014639690811000040</guid><pubDate>Mon, 19 Apr 2010 20:55:00 +0000</pubDate><atom:updated>2010-04-19T13:56:26.834-07:00</atom:updated><title>Avaya PBX admin web service</title><description>At the UW we've been heavily invested in &lt;a href="http://www.avaya.com/usa/"&gt;Avaya&lt;/a&gt; telecommunications products for nearly two decades. In particular, we've been invested in the DEFINITY platform, now called Communications Manager (CM).  We started using CM when it was owned by AT&amp;amp;T. It was later acquired by Lucent and then separated to become Avaya. &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;CM is the cadillac of enterprise phone systems (&lt;a href="http://en.wikipedia.org/wiki/Private_branch_exchange"&gt;PBX&lt;/a&gt;) with almost every feature imaginable.  Like many enterprise vendors, they provide GUI applications written for Windows and they work fine--if they have the features that you want to use.  Once you want to start integrating with other enterprise systems and automating tasks, you run into situations where the provided tools hinder work more than they help.&lt;br /&gt;&lt;br /&gt;The primary interface to these PBX systems is with a terminal.  Originally it was a serial terminal but now telnet and ssh are available.  When you log in you select a "terminal type." Most people use the screen-based VT220 interface where you type commands and use tab and return to move around the screen to make changes.  It's not fun to use those screens for scripting, but there's another undocumented terminal type called "OSSI" that's designed for scripting and is used by Avaya's own GUI tools.&lt;br /&gt;&lt;br /&gt;In this post I'll describe the web service system I've built around the OSSI terminal connection. The end result is contained within a Perl module that comprises the heart of this system.&lt;br /&gt;&lt;br /&gt;The system breaks down into three parts:&lt;br /&gt;&lt;div&gt;&lt;blockquote&gt;1. DEFINITY_ossi.pm&lt;br /&gt;2. A separate PBXD server instance for each PBX.&lt;br /&gt;3. A single web CGI interface that passes XML requests between the client applications and the correct PBXD instance.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://staff.washington.edu/benroy/DEFINITY_ossi.pm"&gt;&lt;/a&gt;&lt;/blockquote&gt;&lt;a href="http://staff.washington.edu/benroy/DEFINITY_ossi.pm"&gt;DEFINITY_ossi.pm&lt;/a&gt; is the Perl module that knows how to connect to all of our PBX systems, process commands, and return data either formatted like the output of the VT220 screen or as a set of OSSI fields.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://staff.washington.edu/benroy/pbxd"&gt;PBXD&lt;/a&gt; is a server based on the &lt;a href="http://search.cpan.org/%7Erhandom/Net-Server-0.97/lib/Net/Server/PreFork.pm"&gt;Net::Server::PreFork&lt;/a&gt; CPAN module that manages a pool of connections to a single PBX and provides an XML interface to DEFINITY_ossi.pm.  There is 1 instance of PBXD running for each of our 3 PBX systems.  Each PBXD instance has its own &lt;a href="http://staff.washington.edu/benroy/pbxd-n1.conf"&gt;config file&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Finally the web interface is a Perl &lt;a href="http://staff.washington.edu/benroy/pbxd-cgi.txt"&gt;CGI script&lt;/a&gt; that knows about each PBXD instance and proxies the XML requests from clients to specific the PBXD instance for each PBX.&lt;br /&gt;&lt;br /&gt;Here's a visual description of the system&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_tQ0FsMlZ7qQ/S8jfIpqhXCI/AAAAAAAAAQw/B3WN1Y3VhP0/s1600/photo.JPG"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 200px; height: 150px;" src="http://2.bp.blogspot.com/_tQ0FsMlZ7qQ/S8jfIpqhXCI/AAAAAAAAAQw/B3WN1Y3VhP0/s200/photo.JPG" alt="" id="BLOGGER_PHOTO_ID_5460859887902874658" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Once it's setup and deployed you have a scalable platform for rapid development of web applications to manage all of your PBX systems.  We've moved both batch and interactive application to the new system and it's been working well.  I hope you've found this post interesting and that it will give you some ideas and code that can help manage you own PBX systems.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-7014639690811000040?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2010/04/avaya-pbx-admin-web-service.html</link><author>noreply@blogger.com (Ben)</author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_tQ0FsMlZ7qQ/S8jfIpqhXCI/AAAAAAAAAQw/B3WN1Y3VhP0/s72-c/photo.JPG' height='72' width='72'/><thr:total>8</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-968584318471948961</guid><pubDate>Thu, 11 Mar 2010 22:09:00 +0000</pubDate><atom:updated>2010-03-11T14:09:18.965-08:00</atom:updated><category domain='http://www.blogger.com/atom/ns#'>sql</category><category domain='http://www.blogger.com/atom/ns#'>http</category><category domain='http://www.blogger.com/atom/ns#'>rest</category><category domain='http://www.blogger.com/atom/ns#'>webservice</category><category domain='http://www.blogger.com/atom/ns#'>rex</category><category domain='http://www.blogger.com/atom/ns#'>sqlx</category><category domain='http://www.blogger.com/atom/ns#'>postgresql</category><title>REX: Database Driven Web Services Service</title><description>&lt;a href="http://sourceforge.net/projects/rex-restsqlxmap/"&gt;REX&lt;/a&gt; presentation given to the University of Washington Web Application Developers meeting:&lt;br /&gt;&lt;br /&gt;&lt;a title="View Rex: Database Driven Web Services Service on Scribd" href="http://www.scribd.com/doc/28230378/Rex-Database-Driven-Web-Services-Service" style="margin: 12px auto 6px auto; font-family: Helvetica,Arial,Sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 14px; line-height: normal; font-size-adjust: none; font-stretch: normal; -x-system-font: none; display: block; text-decoration: underline;"&gt;Rex: Database Driven Web Services Service&lt;/a&gt; &lt;object id="doc_636146292769332" name="doc_636146292769332" height="600" width="100%" type="application/x-shockwave-flash" data="http://d1.scribdassets.com/ScribdViewer.swf" style="outline:none;" &gt;  &lt;param name="movie" value="http://d1.scribdassets.com/ScribdViewer.swf"&gt;&lt;param name="wmode" value="opaque"&gt;&lt;param name="bgcolor" value="#ffffff"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowScriptAccess" value="always"&gt;&lt;param name="FlashVars" value="document_id=28230378&amp;access_key=key-ho86u4yxcc1o95xl5eg&amp;page=1&amp;viewMode=slideshow"&gt;&lt;embed id="doc_636146292769332" name="doc_636146292769332" src="http://d1.scribdassets.com/ScribdViewer.swf?document_id=28230378&amp;access_key=key-ho86u4yxcc1o95xl5eg&amp;page=1&amp;viewMode=slideshow" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="600" width="100%" wmode="opaque" bgcolor="#ffffff"&gt;&lt;/embed&gt;  &lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-968584318471948961?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2010/03/rex-database-driven-web-services.html</link><author>noreply@blogger.com (dharmabruce)</author><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-5846503220308933305</guid><pubDate>Tue, 02 Feb 2010 07:15:00 +0000</pubDate><atom:updated>2010-02-01T23:22:08.886-08:00</atom:updated><title>KPI part 2: materializing views</title><description>In my first KPI post, I mentioned how performance became an issue and how we solved it by pre-generating the results of the summarized data we wanted to display.  That was dandy ... once.  But it was obvious we'd want to do something similar for other data already stored in our database.   As a huge fan of consistency and repeatability, I didn't care for the idea of a mess of tables and another mess of cron jobs and scripts to keep those different tables up to date, each in their own custom way.&lt;br /&gt;&lt;br /&gt;A little research turned up the concept of 'Materialized Views'.  The idea is simple:  generate a view and use it to create and maintain the contents of a static table.  A wide variety of "refesh" techniques can be applied (depending on the importance of having current data and limiting impact on either inserts to the source tables or to the end user viewing the summary.  Some databases implement this natively.  PostgreSQL isn't one of them.&lt;br /&gt;&lt;br /&gt;Some &lt;a href="http://tech.jonathangardner.net/wiki/PostgreSQL/Materialized_Views"&gt;great discussions&lt;/a&gt; of the concept are already available, so I'll just highlight how we chose to implement it.&lt;br /&gt;&lt;br /&gt;First, we created a schema in our 'ironfist' database to contain the views as well as the tables where the materialized content is stored (an MV table) along with a master table too keep track of them:&lt;br /&gt;&lt;PRE&gt;&lt;br /&gt;CREATE SCHEMA matview;&lt;br /&gt;GRANT ALL ON SCHEMA matview TO PUBLIC;&lt;br /&gt;&lt;br /&gt;CREATE TABLE matview.views (&lt;br /&gt;  mv_name NAME NOT NULL PRIMARY KEY&lt;br /&gt;  , v_name NAME NOT NULL&lt;br /&gt;  , last_refresh TIMESTAMP WITH TIME ZONE&lt;br /&gt;);&lt;br /&gt;&lt;/PRE&gt;&lt;br /&gt;For our application, stale data wasn't a concern.  All of our capacity KPIs either show monthly data or show current data.  Regenerating the data weekly (and even the performance hit of regenerating all the data each time) doesn't cost us much.  So the set of functions we used to create a materialized view infrastructure is fairly simple.&lt;br /&gt;&lt;br /&gt;Before starting, we test and create a view in the 'matview' schema then use the various matview functions to create and maintain the MV table. &lt;br /&gt;&lt;br /&gt;This first function creates an MV table based on an existing view.  We tell it the name (which is used to find the view and to create the corresponding table) and the columns we wish to use as the primary key (this helps with future in-place updates).  For example:&lt;br /&gt;&lt;PRE&gt;&lt;br /&gt; select matview.create('kpi_network_switch_ports','sector,month');&lt;br /&gt;&lt;/PRE&gt;&lt;br /&gt;The create function looks like:&lt;br /&gt;&lt;PRE&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION matview.create(name text,primary_cols text) RETURNS VOID AS &lt;br /&gt;$$&lt;br /&gt; DECLARE&lt;br /&gt;    entry matview.views%ROWTYPE;&lt;br /&gt;    view_name text:= 'matview.' || name || '_v';&lt;br /&gt;    matview_name text:= 'matview.' || name || '_mv';&lt;br /&gt; BEGIN&lt;br /&gt;    SELECT * INTO entry FROM matview.views WHERE mv_name = matview_name;&lt;br /&gt;    IF FOUND THEN&lt;br /&gt;         RAISE EXCEPTION 'Materialized view ''%'' already exists.',&lt;br /&gt;           matview_name;&lt;br /&gt;    END IF;  &lt;br /&gt;    EXECUTE 'REVOKE ALL ON '       || view_name ||    ' FROM PUBLIC';&lt;br /&gt;    EXECUTE 'GRANT SELECT ON '     || view_name ||    ' TO PUBLIC'; &lt;br /&gt;    EXECUTE 'CREATE TABLE '        || matview_name || ' AS SELECT * FROM ' || view_name;&lt;br /&gt;    EXECUTE 'ALTER TABLE '         || matview_name || ' ADD PRIMARY KEY ' || '(' || primary_cols || ')';&lt;br /&gt;    EXECUTE 'REVOKE ALL ON '       || matview_name || ' FROM PUBLIC';&lt;br /&gt;    EXECUTE 'GRANT SELECT ON '     || matview_name || ' TO PUBLIC';&lt;br /&gt;    EXECUTE 'CREATE TRIGGER ' || name || '_trigger' || &lt;br /&gt;                ' BEFORE INSERT ON ' || matview_name || &lt;br /&gt;                ' FOR EACH ROW EXECUTE PROCEDURE insert_or_replace_if_duplicate ()';&lt;br /&gt;&lt;br /&gt;    INSERT INTO matview.views (mv_name, v_name, last_refresh)&lt;br /&gt;       VALUES (matview_name, view_name, CURRENT_TIMESTAMP);&lt;br /&gt;     &lt;br /&gt;    RETURN;&lt;br /&gt;END&lt;br /&gt;$$&lt;br /&gt; SECURITY DEFINER&lt;br /&gt; LANGUAGE plpgsql;&lt;br /&gt;&lt;/PRE&gt;&lt;br /&gt;The final insert populates the table with the current results of a SELECT * on the view.   The table can subsequently be 'refreshed' with the matview.refresh function.  This function refreshes either a single table by name or 'all' (all MVs listed in the matview.views table):&lt;br /&gt;&lt;PRE&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION matview.refresh(name text) RETURNS VOID AS &lt;br /&gt;$$&lt;br /&gt;DECLARE&lt;br /&gt;    query text;&lt;br /&gt;    mview matview.views%ROWTYPE;&lt;br /&gt;    matview_name text:= 'matview.' || name || '_mv';&lt;br /&gt;BEGIN&lt;br /&gt;    IF (name = 'all')&lt;br /&gt;    THEN&lt;br /&gt;        query := 'SELECT * from matview.views';&lt;br /&gt;    ELSE&lt;br /&gt;        query := 'SELECT * FROM matview.views WHERE mv_name =' || quote_literal(matview_name);&lt;br /&gt;    END IF;&lt;br /&gt;&lt;br /&gt;    FOR mview in EXECUTE query LOOP&lt;br /&gt;        EXECUTE 'INSERT INTO ' || mview.mv_name || ' SELECT * FROM ' || mview.v_name;&lt;br /&gt;        UPDATE matview.views&lt;br /&gt;            SET last_refresh=CURRENT_TIMESTAMP WHERE mv_name=mview.mv_name;&lt;br /&gt;    END LOOP;&lt;br /&gt;&lt;br /&gt;    IF NOT FOUND THEN&lt;br /&gt;         RAISE EXCEPTION 'Materialized view % does not exist.', matview_name;&lt;br /&gt;    END IF;&lt;br /&gt;&lt;br /&gt;    RETURN;&lt;br /&gt;END;&lt;br /&gt; SECURITY DEFINER&lt;br /&gt; LANGUAGE plpgsql;&lt;br /&gt;&lt;/PRE&gt;&lt;br /&gt;In the initial MV table creation (in the matview.create function), you'll notice that we added a BEFORE INSERT trigger to the table.  When we refresh the table, we update all rows in the table.  Other refresh approaches involve placing triggers on data source tables (we wanted to avoid that, especially since some of our tables are stored on another database server).  &lt;br /&gt;&lt;br /&gt;We could just empty the table and completely regenerate it, but some of our views take several minutes to run.  During that time our table would be empty of data.  And if the view failed (if, say, the dblink connection to our network modeling database failed) the table would remain empty.  Stale data is better than no data. &lt;br /&gt;&lt;br /&gt;Finally, although we have a last refresh time that last refresh time doesn't guarantee that running the view again won't change existing values for existing rows in the table.  For "total number of devices active on the network in January", that number will update on January 1st, then again on January 2nd, etc.  The refresh time is useful for confirming how up to date our data is, but not useful for reducing the work needed to update it.&lt;br /&gt;&lt;br /&gt;So instead, we regenerate all data in an MV, adding a row if no row with the same primary key values exists, but otherwise replacing (not updating!) the matching row with a new one using the following function as a trigger:&lt;br /&gt;&lt;PRE&gt;&lt;br /&gt;CREATE FUNCTION insert_or_replace_if_duplicate() RETURNS trigger&lt;br /&gt;    AS $$&lt;br /&gt;/*  Description:  Trigger function to insert a new row, first deleting if it would violate the primary key&lt;br /&gt;&lt;br /&gt;    To use this function, create a trigger on the appropriate table as follows:&lt;br /&gt;&lt;br /&gt;    CREATE TRIGGER availability_summary_insert_trigger&lt;br /&gt;        BEFORE INSERT ON kpi.availability_summary &lt;br /&gt;        FOR EACH ROW EXECUTE PROCEDURE insert_or_replace_if_duplicate();&lt;br /&gt;&lt;br /&gt;    This before trigger checks to see if the insert would violate the primary key of the table.  &lt;br /&gt;    If it does, it will delete the entire row before returning to the insert call.   This is easier&lt;br /&gt;    than trying to figure out which fields to update.&lt;br /&gt;*/&lt;br /&gt;DECLARE&lt;br /&gt;    table_name text      := TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME;&lt;br /&gt;    where_clause text    := ' WHERE TRUE';&lt;br /&gt;    colname text;&lt;br /&gt;BEGIN&lt;br /&gt;    FOR colname IN  select column_name &lt;br /&gt;                        from information_schema.key_column_usage&lt;br /&gt;                        natural join information_schema.table_constraints itc&lt;br /&gt;                        where itc.table_schema=quote_ident(TG_TABLE_SCHEMA)&lt;br /&gt;                          and itc.table_name=quote_ident(TG_TABLE_NAME)&lt;br /&gt;                          and itc.constraint_type='PRIMARY KEY'&lt;br /&gt;    LOOP&lt;br /&gt;-- the syntax is hairy, but this casts the NEW row to the appropriate table and lets us pick &lt;br /&gt;-- out the columns we want to compare.&lt;br /&gt;&lt;br /&gt;        where_clause := where_clause || ' AND ' || colname || '=' || '(' || quote_literal(new) || '::' || table_name || ').' || colname;&lt;br /&gt;    END LOOP;&lt;br /&gt;&lt;br /&gt;-- now that we know what row we will conflict with, delete it before doing our insert.&lt;br /&gt;&lt;br /&gt;    EXECUTE 'DELETE FROM ' || table_name || where_clause;&lt;br /&gt;    RETURN NEW;&lt;br /&gt;END&lt;br /&gt;$$&lt;br /&gt;    LANGUAGE plpgsql;&lt;br /&gt;&lt;br /&gt;&lt;/PRE&gt;&lt;br /&gt;This works because we are assured that updates will always generate data for all columns.  The above function wouldn't be useful for cases where only some column data was provided for subsequent updates.  &lt;br /&gt;&lt;br /&gt;Finally, of course, we have a function that cleans up any MV tables if we choose to remove them:&lt;br /&gt;&lt;PRE&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION matview.drop(name text) RETURNS VOID&lt;br /&gt; AS &lt;br /&gt;$$&lt;br /&gt; DECLARE&lt;br /&gt;    entry matview.views%ROWTYPE;&lt;br /&gt;    view_name text:= 'matview.' || name || '_v';&lt;br /&gt;    matview_name text:= 'matview.' || name || '_mv';&lt;br /&gt; BEGIN&lt;br /&gt;     SELECT * INTO entry FROM matview.views WHERE mv_name = matview_name;&lt;br /&gt;&lt;br /&gt;     IF NOT FOUND THEN&lt;br /&gt;         RAISE EXCEPTION 'Materialized view % does not exist.', matview_name;&lt;br /&gt;     END IF;&lt;br /&gt;&lt;br /&gt;     EXECUTE 'DROP TABLE ' || matview_name;&lt;br /&gt;     DELETE FROM matview.views WHERE mv_name=matview_name;&lt;br /&gt;     RETURN;&lt;br /&gt; END&lt;br /&gt;$$&lt;br /&gt; SECURITY DEFINER&lt;br /&gt; LANGUAGE plpgsql;&lt;br /&gt;&lt;/PRE&gt;&lt;br /&gt;&lt;br /&gt;The final post in this series will discuss some of what we did with the Google Visualization API to use and display the data from our Materialized Views.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-5846503220308933305?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2010/02/kpi-part-2-materializing-views.html</link><author>noreply@blogger.com (Heather Sherman)</author><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-8883865400601817004</guid><pubDate>Sat, 23 Jan 2010 00:12:00 +0000</pubDate><atom:updated>2010-01-22T16:26:17.063-08:00</atom:updated><title>PostgreSQL fun: cross-database joins</title><description>Before I discuss how we implemented Materialized Views, I should give a little background about our database configuration.  We have two production PostgreSQL databases running on two different servers.  The first database, "ripit_2" is our primary database, containing network modeling information (our devices, their interfaces, network contact people, and all associated attributes) and other critical support information  (e.g. meerkat network events).&lt;br /&gt;&lt;br /&gt;Non-critical, highly active data is stored on our second database, ironfist.&lt;br /&gt;&lt;br /&gt;We've tried to keep our PostgreSQL installation as vanilla as possible.  We rely on a fabulous and responsive team of five DBAs who support not only our PostgreSQL servers, but also many campus MS SQL, IBM DB2, IBM INFORMIX, ORACLE, and SYBASE servers!  To reduce our demands on their limited time and to avoid locking ourselves into a particular database, we historically shied away from installing add-ons or depending on PostgreSQL specific features.&lt;br /&gt;&lt;br /&gt;When the KPI project started, we knew that much of the information it generated would be stored on ironfist, such as the daily device and subnet availability numbers we planned to produce.  And KPI would summarize information already available on ironfist, including arp cache information and switch mac address table information.  Some information would come from the ripit_2 database, but we anticipated storing all the final summary results on ironfist.&lt;br /&gt;&lt;br /&gt;The lack of cross-database joins became a headache almost immediately.  PostgreSQL doesn't natively support cross-database joins, so in the past we did a mixture of replicating static data from ripit_2 into ironfist tables or performing some types of joins into client code (in java, Perl or Python).  There are obvious downsides to both of these approaches, as we wanted to move to a model extensively based on web services using the rex SQL/XML mapper.&lt;br /&gt;&lt;br /&gt;As an experiment we decided to install the dblink contrib module on ironfist.  By doing this, we could have ironfist perform queries against ripit_2 and use the results to join against data in the ironfist database.   We found a couple of helpful articles online.  &lt;a href="http://www.postgresonline.com/journal/index.php?/archives/44-Using-DbLink-to-access-other-PostgreeSQL-Databases-and-Servers.html"&gt;The Postgres OnLine Journal&lt;/a&gt; provided a great set of examples and highlighted some of the limitations.  A typical query might look like:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SELECT *&lt;br /&gt;FROM dblink('dbname=ripit_2 host=dbr port=5432 user=db_kpi password=testing',&lt;br /&gt;       'select make, model from system_id_types')&lt;br /&gt;as p(make text, model text);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So we installed dblink and are now using it for the KPI project extensively.&lt;br /&gt;&lt;br /&gt;The biggest issues we've found with dblink are:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;performance:&lt;/b&gt;  your query must take into account that the full data set from the remote server is going to be retrieved before any joins against local data can happen.  Reduce that data set in your query on the remote server as much as possible.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;authentication:&lt;/b&gt; either you need to be superuser or you need to have the remote db set up to require a password from the source db host/user and that password has to be included in the query.  Yuck!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;explicit structure declarations:&lt;/b&gt; a local ironfist query (or view, or function) that uses a dblink call must explicitly list the row output structure.  Note the use of "(make text, model text)" in the above.&lt;br /&gt;&lt;br /&gt;The only one of these that we addressed in a general way is the authorization issue.   While the KPI project was, so far, the only consumer of dblink queries, we realized that other projects, with different ripit_2 authorization needs on the ironfist end might make use of dblink in future.  So we installed dblink in its own schema (dblink) and added a couple of tables and helper functions.   In addition we set up some authentication infrastructure between our two databases (and between the development and eval systems as well):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;-- created a 'readonly' db group 'db_ro_group'&lt;br /&gt;CREATE ROLE dblink_readonly nologin;                           &lt;br /&gt;-- created a user dblink_kpi_or in the group&lt;br /&gt;CREATE USER dblink_kpi_ro IN ROLE dblink_readonly password 'testing';&lt;br /&gt;&lt;br /&gt;# for dblink access from ironfist database hosts (prod, eval and dev), require md5 passwords&lt;br /&gt;host    all     +dblink_readonly     192.168.1.1   255.255.255.255 md5 #dblink from dbi&lt;br /&gt;host    all     +dblink_readonly     192.168.1.2   255.255.255.255 md5 #dblink from dbieval&lt;br /&gt;host    all     +dblink_readonly     192.168.1.3   255.255.255.255 md5 #dblink from dbidev&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We wanted to ensure that functions would not have to hardcode passwords.  Passwords change and having to replace some unknown number of functions, scattered throughout schemas in the ironfist database someday was a painful thought.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;CREATE schema dblink;&lt;br /&gt;GRANT USAGE ON schema dblink to public;&lt;br /&gt;&lt;br /&gt;CREATE TABLE dblink.users (&lt;br /&gt;id integer NOT NULL,&lt;br /&gt;"user" character varying,&lt;br /&gt;password character varying&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION dblink.connect_ripit_2(text, text) returns text&lt;br /&gt;as $SQL$&lt;br /&gt;SELECT 'dbname=ripit_2 host=' || $2 || ' port=5432 user=' || $1 ||' password=' || password&lt;br /&gt;FROM   dblink.users&lt;br /&gt;WHERE  dblink.users.user = $1&lt;br /&gt;$SQL$&lt;br /&gt;LANGUAGE SQL&lt;br /&gt;SECURITY DEFINER&lt;br /&gt;STABLE;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note the "SECURITY DEFINER" attribute of that function.  We restricted access to the dblink.users table so that only user postgres can select from it.  "SECURITY DEFINER" means that the function dblink.connect_ripit_2 can read the table regardless of who is executing that function.  Someone can run the function, but won't be able to directly view the table of users and passwords.&lt;br /&gt;&lt;br /&gt;Below I'll show one of the shorter KPI views we created which uses dblink.  This view generates a histogram of active devices by type per month per sector for the past 60 months.   The entire query ru ns on the ripit_2 database server (no data from ironfist is joined for this one), using the dev_state and dev_state_history tables.   The raw data looks like:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;sector    |   month    |   device_type   |  make  |  model       | count&lt;br /&gt;-------------+------------+-----------------+--------+--------------+-----------&lt;br /&gt;uwcampus    | 2010-01-01 | layer2          | smc    | swsmc8612t   |     2&lt;br /&gt;uwcampus    | 2010-01-01 | layer2          | smc    | swsmc8612xl3 |   299&lt;br /&gt;uwcampus    | 2010-01-01 | layer2          | smc    | swsmc8624t   |    72&lt;br /&gt;uwcampus    | 2010-01-01 | router          | cisco  | 1811         |     2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And so on.&lt;br /&gt;&lt;br /&gt;Here's a taste of what we can prese nt with the Google Visualization API from the above data:  a stacked bar chart showing co unts of the di fferent device types by month:&lt;br /&gt;&lt;pre&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_Hs9kfQ66HR0/S1pA1ah7dHI/AAAAAAAAAYQ/O7rOZgXBZG8/s1600-h/285232295.878349.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 400px; height: 133px;" src="http://1.bp.blogspot.com/_Hs9kfQ66HR0/S1pA1ah7dHI/AAAAAAAAAYQ/O7rOZgXBZG8/s400/285232295.878349.png" alt="" id="BLOGGER_PHOTO_ID_5429723587147428978" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;A "SELECT *" on the following query takes almost 60 seconds!  "Materializing" this view (by pre-generating its results and storing them in a table on ironfist) were critical to providing a responsive user interface.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;CREATE OR REPLACE VIEW matview.kpi_network_devices_v AS&lt;br /&gt;SELECT * FROM dblink.dblink(dblink.connect_ripit_2('dblink_kpi_ro'::text, 'dbr'::text),&lt;br /&gt;$$  SELECT sector, month, device_type, make,  model,  system_type, count(device_id)&lt;br /&gt;   FROM (  SELECT  CASE WHEN sector.sector = 'mcis'&lt;br /&gt;                            THEN 'uwmedcenter'&lt;br /&gt;                            WHEN sector.owner = 'uw'&lt;br /&gt;                             THEN 'uwcampus'&lt;br /&gt;                             ELSE sector.owner&lt;br /&gt;                       END as sector,&lt;br /&gt;                   intervals.month,&lt;br /&gt;                       device_types.type as device_type,&lt;br /&gt;                       system_id_types.id as system_type,&lt;br /&gt;                       make,&lt;br /&gt;                       model,&lt;br /&gt;                   dev_state.device_id     &lt;br /&gt;           FROM (  SELECT (date_trunc('month', now())::date - s.a * '1 mon'::interval)::date as month&lt;br /&gt;                   FROM generate_series(0,60) as s(a)&lt;br /&gt;               ) as intervals&lt;br /&gt;           JOIN (  SELECT device_id,system_id,installdate, null as modified FROM dev_state&lt;br /&gt;                           UNION&lt;br /&gt;                           SELECT device_id, system_id, installdate,modified FROM dev_state_history&lt;br /&gt;                           WHERE dev_state_history.deleted = true&lt;br /&gt;                   ) AS dev_state&lt;br /&gt;                 ON (dev_state.modified &gt; intervals.month or dev_state.modified is NULL)&lt;br /&gt;                 AND dev_state.installdate &lt; system_id="system_id_types.id" system_type="device_types.id" device_id =" dev_state.device_id" id="dc.sector"&gt;&lt;br /&gt;See our "dblink_connect_ripit_2" function which specifies the user we want to connect as and which database server we want to use?  Note that we can use $$ for quoting here.  My first query was crazy full of escaped quotes until I realized that $$ quoting worked just fine in views as well as functions.&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-8883865400601817004?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2010/01/postgresql-fun-cross-database-joins.html</link><author>noreply@blogger.com (Heather Sherman)</author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_Hs9kfQ66HR0/S1pA1ah7dHI/AAAAAAAAAYQ/O7rOZgXBZG8/s72-c/285232295.878349.png' height='72' width='72'/><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-7544718430755862915</guid><pubDate>Thu, 14 Jan 2010 19:21:00 +0000</pubDate><atom:updated>2010-01-14T12:10:14.047-08:00</atom:updated><category domain='http://www.blogger.com/atom/ns#'>rest</category><category domain='http://www.blogger.com/atom/ns#'>rex</category><category domain='http://www.blogger.com/atom/ns#'>pchart</category><category domain='http://www.blogger.com/atom/ns#'>kpi</category><category domain='http://www.blogger.com/atom/ns#'>google visualization api</category><category domain='http://www.blogger.com/atom/ns#'>xml</category><category domain='http://www.blogger.com/atom/ns#'>postgresql</category><category domain='http://www.blogger.com/atom/ns#'>uw_tech_tools</category><category domain='http://www.blogger.com/atom/ns#'>meerkat</category><title>KPI part 1:  database/XML mapping populates Key Performance Data charts</title><description>Recently we were asked to develop a page of Key Performance Indicator charts showing network availability and other network capacity statistics.  We have a lot of historical data, scattered throughout flat files, RRD files and in our PostgreSQL database.  In the past we've responded to similar requests by producing ad-hoc charts and tables, ending up with several web pages with disparate look and feel and levels of detail.&lt;br /&gt;&lt;br /&gt;This time, we wanted to come up with a complete system that could be re-used; settling on a preferred (preferably javascript-based) flexible charting system, a consistent look and feel and a manageable way of storing the underlying data.  Ideally some of those ad-hoc charts would even be migrated to this new system.&lt;br /&gt;&lt;br /&gt;Just identifying how to calculate the data for the initial KPI was challenging (when you measure average network availability for a campus network, where do you measure?  Do you weight the 'service points' equally?  And what's the smallest meaningful time range to show?)&lt;br /&gt;&lt;br /&gt;We determined that UW campus network availability would be measured by 'average uptime of unweighted subnet prefix' and that we would show average availability over week, quarter and year.&lt;br /&gt;&lt;br /&gt;So, where do we get this data?  We merge it from several of our production network management tools:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Database&lt;/b&gt; We use a PostgreSQL database to model our network.  The Network Operations Center maintains tables of devices, interfaces, networks, contacts, events and all their related attributes, using a mixture of manual addition and automated device discovery systems.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Meerkat historical data&lt;/b&gt; Meerkat is our locally developed open source network monitoring system.  It tracks all network events (writing them to the database) and generates alerts for the Network Operations Center.  We have a companion web service which can provide availability information for devices or network prefixes from the detected network events.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Meerkat availability service&lt;/b&gt; The companion availability service reports raw availability for a given device or network prefix.  Device availability is easy:  if there's an event showing the device was unavailable in our database, its raw availability for the year is reduced by the length of the outage window.  Determining network availability requires topology discovery and awareness of redundant paths.  Only if outage events occur interrupting all paths from the monitoring system to the network will the raw availability of the network be reduced.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;RT::Outages planned event tickets&lt;/b&gt; RT::Outages is locally written tool which interacts with the &lt;a href="http://bestpractical.com/rt/"&gt;RT trouble ticket system&lt;/a&gt;.  It provides a workbench out network operations staff can use to generate outage reports.  The workbench allows selection of devices and interfaces involved in an outage and provides a date picker for consistent entry of outage window start and end times.  Contact lists are automatically generated from the database device and network contact lists.  And, essential for the KPI project, RT::Outages is also used to schedule planned events.&lt;br /&gt;&lt;br /&gt;For the KPI project, some changes had to be made to enhance the Meerkat availability service.  Availability isn't conventionally calculated on all network downtime; just on unplanned outages.  The RT::Outages service has that information available and we use that to permit the Meerkat availability service to exclude those events.  In addition, the service only provided an annual availability measurement.  To get quarterly and weekly measurements we updated it to accept arbitrary time ranges.&lt;br /&gt;&lt;br /&gt;Now that we knew where to get our raw information what would we do with it?&lt;br /&gt;&lt;br /&gt;We started by writing a nightly tool to fetch the availability of each of the "service points" on our network (service points are network prefixes for the UW campus and UW Medical Centers networks, sites for the Washington K20 networks).  To enable this, we made use of another locally developed open source initiative: &lt;br /&gt;&lt;br /&gt;&lt;b&gt;REX&lt;/b&gt; &lt;a href="https://sourceforge.net/projects/rex-restsqlxmap/"&gt;REX&lt;/a&gt; is a SQL/XML mapper that provides a framework for turning PostgreSQL database queries into XML documents available through a REST service.  It leverages the XML functions available in PostgreSQL.  In addition to fetching information from the database this way, we can also update the database.&lt;br /&gt;&lt;br /&gt;The nightly process, for example, is a Perl program that connects to a REX-based REST service for the list of service points, processes each service point by sending a request to the Meerkat availability service for the availability for the previous day, then uses XML::Simple to generate an XML document to post back to a final service which updates the database with all the service point measurements.  Given a hash of measurements:&lt;small&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$VAR1 = {&lt;br /&gt;        'measurement' =&gt; [&lt;br /&gt;                           {&lt;br /&gt;                             'owner' =&gt; 'uwcampus',&lt;br /&gt;                             'availability' =&gt; '100.0',&lt;br /&gt;                             'includes_planned_events' =&gt; 'false',&lt;br /&gt;                             'service_point' =&gt; '128.95.138.0/24',&lt;br /&gt;                             'datetime' =&gt; '2010-01-12'&lt;br /&gt;                           }&lt;br /&gt;                         ]&lt;br /&gt;      };&lt;br /&gt;&lt;/pre&gt;&lt;/small&gt;&lt;br /&gt;We update the database with the following XML document:&lt;small&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;measurements&amp;gt;&lt;br /&gt; &amp;lt;measurement availability="100.0" datetime="2010-01-12" includes_planned_events="false" owner="uwcampus" service_point="128.95.138.0/24" /&amp;gt;&lt;br /&gt;&amp;lt;/measurements&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/small&gt;&lt;br /&gt;using the following code, using Perl, XML::Simple and LWP::UserAgent::Determined:&lt;small&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;post_measurements( { ua =&gt; $ua,&lt;br /&gt;                       post_document =&gt; XMLout ($measurements, RootName=&gt;'measurements')&lt;br /&gt;} );&lt;br /&gt;&lt;br /&gt;sub post_measurements {&lt;br /&gt;  my ($param_ref) = @_;&lt;br /&gt;  my $post_document = $param_ref-&gt;{post_document} || croak 'No document to post';&lt;br /&gt;&lt;br /&gt;  my $ua       = $param_ref-&gt;{ua} || LWP::UserAgent::Determined-&gt;new;&lt;br /&gt;  my $url      = "https://$options{ws}/rex/xml/kpi_availability/ADD_DATA";&lt;br /&gt;  my $request  = HTTP::Request-&gt;new('POST', $url);&lt;br /&gt;&lt;br /&gt;  $request-&gt;content_type('application/xml');&lt;br /&gt;  $request-&gt;content($post_document);&lt;br /&gt;  my $response = $ua-&gt;request($request);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/small&gt;&lt;br /&gt;Typically we send multiple measurements at a time.   This produces a table full of measurements, one per day per service point. &lt;br /&gt;&lt;br /&gt;When we display them, however, we want to summarize the daily service_point measurements to weekly, quarterly and annual measurements for an entire collection of service points (e.g. the service points in the UW campus network or in the K20 statewide network).  For our prototype, we intended for the front end to process XML, so we wrote a PL/pgSQL function (see end of post) to generate an XML document.  The REX-based service supplying data to the front end was configured to run this on demand.  For more information on using the xml functions in PostgreSQL, see http://www.postgresql.org/docs/8.3/interactive/functions-xml.html&lt;br /&gt;&lt;br /&gt;We soon found that running this on demand was much too slow (seven seconds!) and that the &lt;a href="http://pchart.sourceforge.net/"&gt;pchart&lt;/a&gt; system we initially used didn't provide all the features we wanted and locked us in to PHP. &lt;br /&gt;&lt;br /&gt;Stay tuned; in my next posts I'll outline how we implemented Materialized Views to rescue us from performance problems like those experienced above and how we implemented our final Javascript-based front-end using Google Charts via the Google Visualization API.&lt;small&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;CREATE FUNCTION rex_fn.kpi_availability_read(owner_selected text, year_selected text,&lt;br /&gt;                                            include_planned_events boolean)&lt;br /&gt;RETURNS xml&lt;br /&gt;AS $$&lt;br /&gt;BEGIN&lt;br /&gt;/* FUNCTION:     rex_fn.kpi_availability_read()&lt;br /&gt;  Description:  Summarizes weekly, quarterly and yearly availability measurements&lt;br /&gt;  Affects:      Makes no changes to the data    &lt;br /&gt;  Arguments:    name of a supported owner, a year and whether or not the measurements should include or&lt;br /&gt;                exclude planned event windows&lt;br /&gt;  Returns:      xml document like:&lt;br /&gt;&lt;br /&gt;&amp;lt;availability&amp;gt;&lt;br /&gt;  &amp;lt;yearlyavailability&amp;gt;&lt;br /&gt;      &amp;lt;plannedincluded&amp;gt;true&amp;lt;/plannedincluded&amp;gt;&lt;br /&gt;      &amp;lt;owner&amp;gt;&lt;br /&gt;          &amp;lt;name&amp;gt;k20&amp;lt;/name&amp;gt;&lt;br /&gt;          &amp;lt;year&amp;gt;&lt;br /&gt;              &amp;lt;number&amp;gt;2009&amp;lt;/number&amp;gt;&lt;br /&gt;              &amp;lt;averageavailability&amp;gt;99.8159518537013916&amp;lt;/averageavailability&amp;gt;&lt;br /&gt;              &amp;lt;quarter&amp;gt;&lt;br /&gt;                  &amp;lt;number&amp;gt;1&amp;lt;/number&amp;gt;&lt;br /&gt;                  &amp;lt;averageavailability&amp;gt;99.8047910271571390&amp;lt;/averageavailability&amp;gt;&lt;br /&gt;                  &amp;lt;complete&amp;gt;true&amp;lt;/complete&amp;gt;&lt;br /&gt;                  &amp;lt;week&amp;gt;&lt;br /&gt;                      &amp;lt;number&amp;gt;01&amp;lt;/number&amp;gt;&lt;br /&gt;                      &amp;lt;averageavailability&amp;gt;99.6165324837199837&amp;lt;/averageavailability&amp;gt;&lt;br /&gt;                      &amp;lt;start_date&amp;gt;1230796800&amp;lt;/start_date&amp;gt;&lt;br /&gt;                      &amp;lt;end_date&amp;gt;1231315200&amp;lt;/end_date&amp;gt;&lt;br /&gt;                      &amp;lt;complete&amp;gt;true&amp;lt;/complete&amp;gt;&lt;br /&gt;                  &amp;lt;/week&amp;gt;&lt;br /&gt;              &amp;lt;/quarter&amp;gt;&lt;br /&gt;          &amp;lt;/year&amp;gt;&lt;br /&gt;      &amp;lt;/owner&amp;gt;&lt;br /&gt;  &amp;lt;yearlyavailability&amp;gt;&lt;br /&gt;&amp;lt;/availability&amp;gt;&lt;br /&gt;*/&lt;br /&gt;BEGIN&lt;br /&gt;  RETURN xmlelement (name availability,&lt;br /&gt;                     xmlagg(availability_frag)&lt;br /&gt;         )&lt;br /&gt;  FROM (  SELECT xmlelement (name yearlyAvailability,&lt;br /&gt;                             xmlconcat (xmlelement (name plannedIncluded, include_planned_events),&lt;br /&gt;                                        xmlagg (xmlelement (name owner, owner_frag))&lt;br /&gt;                             )&lt;br /&gt;                 ) AS availability_frag&lt;br /&gt;          FROM (  SELECT xmlconcat(xmlelement (name name, owner),&lt;br /&gt;                                   xmlagg(xmlelement (name year, year_frag))&lt;br /&gt;                         ) AS owner_frag&lt;br /&gt;                  FROM (  SELECT owner, year, xmlconcat (xmlelement (name number, year),&lt;br /&gt;                                                         xmlagg (quarter_frag)&lt;br /&gt;                                              ) AS year_frag&lt;br /&gt;-- Clause to produce the "yearly availability"&lt;br /&gt;                          FROM (  SELECT owner,&lt;br /&gt;                                         to_char(datetime,'YYYY') AS year,&lt;br /&gt;                                         xmlelement ( name averageAvailability,avg(availability) ) AS quarter_frag&lt;br /&gt;                                  FROM kpi.availability&lt;br /&gt;                                  WHERE owner = owner_selected&lt;br /&gt;                                    AND includes_planned_events = include_planned_events&lt;br /&gt;                                    AND to_char(datetime,'YYYY') = year_selected&lt;br /&gt;                                  GROUP BY owner, year&lt;br /&gt;                                UNION ALL&lt;br /&gt;-- Call helper function to produce the xml for the "quarterly availability"&lt;br /&gt;                                  SELECT *&lt;br /&gt;                                  FROM rex_fn.kpi_availability_read_quarter (owner_selected,&lt;br /&gt;                                                                             year_selected,&lt;br /&gt;                                                                             include_planned_events)&lt;br /&gt;                          ) AS sub4 GROUP BY owner, year&lt;br /&gt;                  ) AS sub3 GROUP BY owner&lt;br /&gt;          ) AS sub2&lt;br /&gt;  ) AS sub1;&lt;br /&gt;END;&lt;br /&gt;$$&lt;br /&gt;  LANGUAGE plpgsql;&lt;br /&gt;&lt;br /&gt;CREATE FUNCTION rex_fn.kpi_availability_read_quarter(owner_selected text, year_selected text,&lt;br /&gt;                                                    include_planned_events boolean)&lt;br /&gt;RETURNS SETOF kpi_availability_quarter_record_type&lt;br /&gt;  AS $$&lt;br /&gt;/*  FUNCTION:     rex_fn.kpi_availability_read_quarter()&lt;br /&gt;  Description:  helper function for rex_fn.kpi_availability_read -- generates quarterly xml subtree&lt;br /&gt;&lt;br /&gt;  Affects:      Makes no changes to the data    &lt;br /&gt;  Arguments:    name of a supported owner, a year and whether or not the measurements should include or&lt;br /&gt;                exclude planned event windows&lt;br /&gt;  Returns:      set of xml documents&lt;br /&gt;*/&lt;br /&gt;BEGIN&lt;br /&gt;  RETURN QUERY    SELECT owner, year, xmlelement(name quarter, xmlconcat (xmlelement (name number, quarter),&lt;br /&gt;                                                                          xmlagg (week_frag)&lt;br /&gt;                                                               )&lt;br /&gt;                                                    ) AS quarter_frag&lt;br /&gt;-- Clause to produce the "quarterly availability"&lt;br /&gt;                  FROM (  SELECT owner, to_char(datetime,'YYYY') AS year, to_char(datetime, 'Q') as quarter,&lt;br /&gt;                                 xmlconcat (xmlelement ( name averageAvailability,avg(availability) ),&lt;br /&gt;                                            xmlelement ( name complete,&lt;br /&gt;                                                         CASE WHEN to_char(max(datetime),'Q') != to_char( max(datetime) + interval '1 day','Q')&lt;br /&gt;                                                              THEN TRUE&lt;br /&gt;                                                              ELSE FALSE END&lt;br /&gt;                                            )&lt;br /&gt;                                 ) AS week_frag&lt;br /&gt;                          FROM kpi.availability&lt;br /&gt;                          WHERE owner = owner_selected&lt;br /&gt;                            AND includes_planned_events = include_planned_events&lt;br /&gt;                            AND to_char(datetime,'YYYY') = year_selected&lt;br /&gt;                          GROUP BY owner, year, quarter&lt;br /&gt;                        UNION ALL&lt;br /&gt;-- Call helper function to produce the xml for the "weekly availability"&lt;br /&gt;                          SELECT * FROM rex_fn.kpi_availability_read_week (owner_selected,&lt;br /&gt;                                                                           year_selected,&lt;br /&gt;                                                                           include_planned_events)&lt;br /&gt;                  ) AS sub GROUP BY owner, year, quarter ORDER BY year, quarter ASC;&lt;br /&gt;  RETURN;&lt;br /&gt;END;&lt;br /&gt;$$ &lt;br /&gt;  LANGUAGE plpgsql;&lt;br /&gt;&lt;br /&gt;CREATE FUNCTION kpi_availability_read_week(owner_selected text, year_selected text, include_planned_events boolean)&lt;br /&gt;RETURNS SETOF kpi_availability_week_record_type&lt;br /&gt;  AS $$&lt;br /&gt;/*  FUNCTION:     rex_fn.kpi_availability_read_week()&lt;br /&gt;  Description:  helper function for rex_fn.kpi_availability_read -- generates weekly xml subtree&lt;br /&gt;  Affects:      Makes no changes to the data    &lt;br /&gt;  Arguments:    name of a supported owner, a year and whether or not the measurements should include or&lt;br /&gt;                exclude planned event windows&lt;br /&gt;  Returns:      set of xml documents&lt;br /&gt;*/&lt;br /&gt;BEGIN&lt;br /&gt;  RETURN QUERY    SELECT owner,&lt;br /&gt;                         year,&lt;br /&gt;                         quarter,&lt;br /&gt;                         xmlelement(name week, xmlconcat (xmlelement (name number, week),&lt;br /&gt;                             xmlagg ( xmlelement (name averageAvailability, averageAvailability) ),&lt;br /&gt;                             xmlagg ( xmlelement (name start_date, extract (epoch from start_date)) ),&lt;br /&gt;                             xmlagg ( xmlelement (name end_date,  extract (epoch from end_date)) ),&lt;br /&gt;                             xmlagg ( xmlelement (name complete,&lt;br /&gt;                                        CASE WHEN start_date + interval '1 week' = end_date + interval '1 day'&lt;br /&gt;                                             THEN TRUE&lt;br /&gt;                                             ELSE FALSE END)&lt;br /&gt;                                     ) -- xml element week&lt;br /&gt;                             ) -- xmlconcat&lt;br /&gt;                         ) AS week_frag&lt;br /&gt;                  FROM (  SELECT  owner                                               AS owner,&lt;br /&gt;                                  to_char(week_start_date(datetime,'Mon'),'YYYY')     AS year,&lt;br /&gt;                                  to_char(week_start_date(datetime,'Mon'), 'Q')       AS quarter,&lt;br /&gt;                                  to_char(week_start_date(datetime,'Mon'),'WW')       AS week,&lt;br /&gt;                                  min(datetime)                                       AS start_date,&lt;br /&gt;                                  max(datetime)                                       AS end_date,&lt;br /&gt;                                  avg(availability)                                   AS averageAvailability&lt;br /&gt;                          FROM kpi.availability&lt;br /&gt;                          WHERE owner = owner_selected&lt;br /&gt;                            AND includes_planned_events = include_planned_events&lt;br /&gt;                            AND to_char(week_start_date(datetime,'Mon'),'YYYY') = year_selected&lt;br /&gt;                          GROUP BY owner, year, quarter, week&lt;br /&gt;                  ) AS sub&lt;br /&gt;                  GROUP BY owner, year, quarter, week&lt;br /&gt;                  ORDER BY quarter, week ASC;&lt;br /&gt;  RETURN;&lt;br /&gt;END;&lt;br /&gt;$$&lt;br /&gt;  LANGUAGE plpgsql;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/small&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-7544718430755862915?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2010/01/kpi-part-1-databasexml-mapping_3434.html</link><author>noreply@blogger.com (Heather Sherman)</author><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-653290361425524538</guid><pubDate>Tue, 17 Nov 2009 23:27:00 +0000</pubDate><atom:updated>2009-11-17T16:25:39.106-08:00</atom:updated><title>jPop: Binding Javascript Objects to HTML Automatically</title><description>jPop is a jQuery plugin that will automatically bind data from javascript object to the contents of html elements.  Example:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;html&amp;gt;&lt;br /&gt; &amp;lt;head&amp;gt;&lt;br /&gt;  &amp;lt;title&amp;gt;jPop Example&amp;lt;/title&amp;gt;&lt;br /&gt;  &amp;lt;script src=&amp;quot;/js/jquery/1.3.2/jquery-1.3.2.min.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;  &amp;lt;script src=&amp;quot;/js/jquery.jpop.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;/head&amp;gt;&lt;br /&gt; &amp;lt;body&amp;gt;&lt;br /&gt;  &amp;lt;h1 id=&amp;quot;greeting&amp;quot; class=&amp;quot;jpop_class&amp;quot;&amp;gt;&amp;lt;/h1&amp;gt;&lt;br /&gt;  &amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;&lt;br /&gt;    $(function() {&lt;br /&gt;      var jso= { greeting: &amp;quot;Hello, World!&amp;quot; };&lt;br /&gt;      $('.jpop_class').jpop(jso);&lt;br /&gt;    });&lt;br /&gt;  &amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://dl.dropbox.com/u/174812/jquery.jpop.js"&gt;Download jPop.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;jPop works with any javascript object and doesn't put restrictions on the data structure.  Here is a more complicated example which demonstrates how jpop handles complex data structures and arrays:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;html&amp;gt;&lt;br /&gt; &amp;lt;head&amp;gt;&lt;br /&gt;  &amp;lt;title&amp;gt;jPop Example&amp;lt;/title&amp;gt;&lt;br /&gt;  &amp;lt;script src=&amp;quot;/js/jquery/1.3.2/jquery-1.3.2.min.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;  &amp;lt;script src=&amp;quot;/js/jquery.jpop.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;/head&amp;gt;&lt;br /&gt; &amp;lt;body&amp;gt;&lt;br /&gt;  &amp;lt;h1 id=&amp;quot;greeting&amp;quot; class=&amp;quot;jpop_class&amp;quot;&amp;gt;&amp;lt;/h1&amp;gt;&lt;br /&gt;  &amp;lt;p id=&amp;quot;greetingProp.greeting&amp;quot; class=&amp;quot;jpop_class&amp;quot;&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;  &amp;lt;p&amp;gt;And,&lt;br /&gt;   &amp;lt;span id=&amp;quot;greetingProp2:greetingArray&amp;quot; class=&amp;quot;jpop_class&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;br&amp;gt;&amp;lt;span id=&amp;quot;greetingArray.greeting&amp;quot; class=&amp;quot;jpop_class&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;   &amp;lt;/span&amp;gt;&lt;br /&gt;  &amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;&lt;br /&gt;    $(function() {&lt;br /&gt;      var jso= {&lt;br /&gt;          greeting: &amp;quot;Hello, World!&amp;quot;,&lt;br /&gt;          greetingProp: {&lt;br /&gt;              greeting: &amp;quot;Hello again, World!&amp;quot;&lt;br /&gt;          },&lt;br /&gt;          greetingProp2: {&lt;br /&gt;              greetingArray: [&lt;br /&gt;                  { greeting: &amp;quot;Hello, Nick!&amp;quot; },                  &lt;br /&gt;                  { greeting: &amp;quot;Hello, Sri!&amp;quot; },&lt;br /&gt;                  { greeting: &amp;quot;Hello, Jody!&amp;quot; },&lt;br /&gt;                  { greeting: &amp;quot;Hello, Andrew!&amp;quot; },&lt;br /&gt;                  { greeting: &amp;quot;Hello, Heather!&amp;quot; },&lt;br /&gt;                  { greeting: &amp;quot;Hello, Ben!&amp;quot; }&lt;br /&gt;              ]&lt;br /&gt;          }&lt;br /&gt;      };&lt;br /&gt;      $('.jpop_class').jpop(jso);&lt;br /&gt;    });&lt;br /&gt;  &amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The primary convention is that html element ids are used to reference into the javascript object.  In the first example, 'greeting' is a property on the javscript object 'jso' which has the string "Hello, World!".  When an html id contains a ':' that will cause the &lt;strong&gt;containing&lt;/strong&gt; html to be cloned and appended to the element and the property name after the ':' can be used to refer to elements of the array within the contained html.&lt;br /&gt;&lt;br /&gt;jpop only binds to the elements selected by the jQuery expression.  It is not required to use the 'jpop_class" or any other class.&lt;br /&gt;&lt;br /&gt;If html form elements are jpopped, the value attribute will be set.  Further, when the form is filled out (i.e. a 'change' event occurs), jpop will automatically modify the javascript object with the new form values.  Example:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;html&amp;gt;&lt;br /&gt; &amp;lt;head&amp;gt;&lt;br /&gt;  &amp;lt;title&amp;gt;jPop Example&amp;lt;/title&amp;gt;&lt;br /&gt;  &amp;lt;script src=&amp;quot;/js/jquery/1.3.2/jquery-1.3.2.min.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;  &amp;lt;script src=&amp;quot;/js/jquery.jpop.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;/head&amp;gt;&lt;br /&gt; &amp;lt;body&amp;gt;&lt;br /&gt;  &amp;lt;form&amp;gt;&lt;br /&gt;   &amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;greeting&amp;quot; class='jpop_class'&amp;gt;&lt;br /&gt;   &amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;modify greeting&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;/form&amp;gt;&lt;br /&gt;  &amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;&lt;br /&gt;    $(function() {&lt;br /&gt;      var jso= {&lt;br /&gt;          greeting: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;      };  &lt;br /&gt;      $('.jpop_class').jpop(jso);&lt;br /&gt;      $(':submit').click(function() {&lt;br /&gt;          alert(jso.greeting);&lt;br /&gt;          return false; &lt;br /&gt;      });&lt;br /&gt;    });&lt;br /&gt;  &amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-653290361425524538?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2009/11/jpop-binding-javascript-objects-to-html.html</link><author>noreply@blogger.com (dharmabruce)</author><thr:total>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-2932097807811939207.post-542927533829878091</guid><pubDate>Mon, 19 Oct 2009 23:16:00 +0000</pubDate><atom:updated>2009-10-20T15:22:19.015-07:00</atom:updated><category domain='http://www.blogger.com/atom/ns#'>javascript</category><category domain='http://www.blogger.com/atom/ns#'>rest</category><category domain='http://www.blogger.com/atom/ns#'>networktopology</category><category domain='http://www.blogger.com/atom/ns#'>bgp</category><category domain='http://www.blogger.com/atom/ns#'>postgresql</category><category domain='http://www.blogger.com/atom/ns#'>uw_tech_tools</category><category domain='http://www.blogger.com/atom/ns#'>meerkat</category><title>Project Updates</title><description>It seems like every moment is only more busy than the last.  We are working on Network Systems Key Performance Indicators reports (KPI; what we do and how well we do it), Circuit/Network Diagrams in a web-friendly manner (LinkViz), Equipment (Asset) Database  (EDB) focusing on tracking costs and budgeting, and BGP monitoring (Meerkat BGP).  Facing inward, we're improving the efficiency of developing web services built on our postgresql database (rex and automatic html/javascript object binding).&lt;br /&gt;&lt;br /&gt;The primary goal of the KPI project is to report on the depth and breadth of Network Systems services and, further, report the quality of those services.  Service availability was the first chart we deployed.  The user can include or exclude planned outage windows from the report.  The variable represents the average of all customers' availability.  An individual customer's availability is defined as the percentage of time the customer's service point (gateway) was determined to be available (pingable from our poller).&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_i9w_MdCzOxg/St3V-tIFn2I/AAAAAAAAAgs/UPHxyPAPZA8/s1600-h/kpi.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 310px;" src="http://3.bp.blogspot.com/_i9w_MdCzOxg/St3V-tIFn2I/AAAAAAAAAgs/UPHxyPAPZA8/s320/kpi.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5394703201901846370" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;In our LinkViz project, we are making another attempt at automatically visualizing circuits and networks in a way that a Network Engineer might diagram.  In my opinion, automatic Network Topology "christmas trees" have always gotten the problem wrong.  It looks like a Software Engineer is solving the problem using Software Engineering visualizations from graph theory instead of trying to mimic what a Network Architect or Engineer would draw for themselves and their peers.  This problem is ripe for innovation and we're trying to take a baby step towards xanadu here.  We considered using flash, directly generate a Visio file, and several javascript canvas libraries before deciding to go with the html/css box modeling directly.  We need to refine the layout but here's a simple example showing where we are now:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_i9w_MdCzOxg/Stz5mBUBybI/AAAAAAAAAgk/fFY8quM6W70/s1600-h/linkviz.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 71px;" src="http://4.bp.blogspot.com/_i9w_MdCzOxg/Stz5mBUBybI/AAAAAAAAAgk/fFY8quM6W70/s320/linkviz.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5394460885265795506" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The UW Technology Equipment Database project is an effort to converge the different databases from each unit into a single database so that costs can be centrally tracked and we can improve our equipment budget projections.  The new functionality is to track next-step lifecycle decisions such as retire, replace, etc., with a date when action needs to occur.&lt;br /&gt;&lt;br /&gt;We were surprised to find that we were monitoring nearly 1000 BGP sessions.  While we've been managing the bgp monitoring config manually, I had no idea there were this many already.  This project is to automate BGP session discovery and monitoring configuration.&lt;br /&gt;&lt;br /&gt;Facing inward, we've taken several steps to improve how efficiently we develop open, ReST-like, database backed web services.  We try to spend a sensible amount of time improving our common tasks so we can deliver them rapidly.  Two successes here "rex" and html/javascript binding.&lt;br /&gt;&lt;br /&gt;Rex allows you to map ReST HTTP requests directly to database queries that generate XML.  Since we write a lot of services that simply do a simple transformation on the database data and expose it in a ReST-like manner, it was important for us to normalize how we do this and make it more efficient.  After setting up and configuring rex with your jdbc datasources, you can start configuring HTTP endpoints directly to queries.  Once a RexMap is created, your web service is immediately available.  Rex also provides a json endpoint that exposes the badgerfish transformed xml data.  Rex exposes a test interface for entering parameters and displaying the syntax highlighted xml or json results.  &lt;a href="https://sourceforge.net/projects/rex-restsqlxmap/"&gt;It is hosted on sourceforge.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The last efficiency improvement we've gained is using a library that allows you to bind a javascript object (jso) to html directly.  It's kind of like XPath and XSLT but using a jso instead of XML and html directly instead of XSLT.  It is very similar to the chain.js jquery plugin.  The downside of &lt;a href="http://wiki.github.com/raid-ox/chain.js/demos"&gt;chain.js&lt;/a&gt; is that it requires you to format your data in the way it expects (e.g. arrays within the jso must have the key "items").  The html/jso binding approach we took was to simply match the html id to the jso "key".&lt;br /&gt;&lt;br /&gt;For example, to bind an html class "equipment_bind" to a javascript object asset, you do this:&lt;br /&gt;var asset={ Name: "ucswww" };&lt;br /&gt;var equipment=HtmlJsoBind("equipment_bind", asset);&lt;br /&gt;equipment.bind();&lt;br /&gt;&lt;br /&gt;The html would look like this:&lt;br /&gt;&amp;lt;p id="Asset.Name" class="equipment_bind"&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&lt;br /&gt;After equipment.bind() is called, the CDATA portion of the paragraph will be filled with the Asset.Name "ucswww".&lt;br /&gt;&lt;br /&gt;The primary win here is that you don't have a ton of javascript code that is simply creating html to wrap your data.  Our library also handles arrays.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2932097807811939207-542927533829878091?l=tools.cac.washington.edu' alt='' /&gt;&lt;/div&gt;</description><link>http://tools.cac.washington.edu/2009/10/network-management-tools-project.html</link><author>noreply@blogger.com (dharmabruce)</author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_i9w_MdCzOxg/St3V-tIFn2I/AAAAAAAAAgs/UPHxyPAPZA8/s72-c/kpi.png' height='72' width='72'/><thr:total>0</thr:total></item></channel></rss>
