tag:blogger.com,1999:blog-23416864453923564002023-06-20T09:46:25.773-04:00window puttya computing bloggreghttp://www.blogger.com/profile/10634561928684519035noreply@blogger.comBlogger6125tag:blogger.com,1999:blog-2341686445392356400.post-1315065977071856652011-12-14T09:57:00.000-05:002011-12-14T15:00:41.955-05:00debian pxe boot installation with dhcp (dnsmasq) running on an openwrt
router<div class="section" id="debian-stable-pxe-boot-install-with-openwrt-dnsmasq-router-dhcp">
<div class="section" id="background">
<h2>background</h2>
<p>I recently installed debian stable/squeeze on an old-<em>ish</em> laptop. The machine
was already running squeeze, but I wanted to upgrade the hard drive from the
original 80GB to a larger 250GB drive. I had an extra 250GB drive that came
with my new laptop, but I replaced it with a 500GB drive as soon as I bought
it.</p>
<p>The old laptop hard drive had an existing windows XP NTFS partition that I
wanted to copy over to the 250GB drive before I installed debian. I didn't want
to copy over the debian install from the 80GB drive. The fresh install on the
old laptop was intended as a replacement machine for my wife's even-older
laptop, which has started freezing frequently due to what I believe to be a
failing motherboard. My wife doesn't want any of the developer stuff that I
had on my old debian system. Instead, I was going to copy her data over
from her laptop. Confusing enough?</p>
</div>
<div class="section" id="complications">
<h2>complications</h2>
<p>I downloaded a x86 debian squeeze net-install image and copied it to a USB
pen drive, but I discovered that, incredibly, my old laptop BIOS would not
boot from USB. Lame.</p>
</div>
<div class="section" id="linux-installation-methods">
<h2>linux installation methods</h2>
<p>I've installed various linux distros over the years. The most complicated
one was probably a slackware installation onto a 486 laptop with no
optical drive, USB, or ethernet. I started out by booting an installer
bootstrap image from a 3.5" floppy disk and finished that installation by
mounting the rest of the installation media over NFS using a Null-Printer
parallel cable with PLIP networking.</p>
<p>I've also set up automated kickstart installs of centos guests on Xen servers,
and I've installed SuSE servers located halfway across the USA remotely over an
IPMI console. I've installed headless servers from standard boot media using a
console provided by a serial null-modem cable. In addition, I've done the
usual, simple installs from optical media or USB. However, I had never
installed linux from a PXE boot.</p>
<p>Over the years, I've burned way too many linux installation CDRs that I've used
once, put in a desk drawer, and then threw away a couple years later when they
were obsolete by several versions. I hate wasting stuff, and I decided to
finally get around to learning about PXE booting so I can stop throwing away
CDRs after a single use. <em>Caveat</em>: I realize that, going forward, almost all
computers will support booting from USB, and for 1-off installations, that's
the easiest option when it is available. In any case, it was not available for
my old laptop.</p>
</div>
<div class="section" id="pxe-boot-setup">
<h2>pxe boot setup</h2>
<p><a class="reference external" href="https://en.wikipedia.org/wiki/Preboot_Execution_Environment">Preboot Execution Environment (PXE) booting</a> uses DHCP
to discover PXE boot images available on the network. On my home network,
I use a wireless router that runs openwrt for DHCP. I also have a little
fanless, low-power mini-itx x86 server that I use as a file/media server.</p>
<p>I wanted to configure my openwrt DHCP server to direct PXE clients
to installation media on my mini-itx server (hostname == "pizza").</p>
<p>Here is what I had to do:</p>
<div class="section" id="openwrt">
<h3>openwrt</h3>
<p>Add this to <tt class="docutils literal">/etc/config/dhcp</tt>:</p>
<pre class="literal-block">
#
# Specify pxelinux.0 without a directory prefix
# because we run tftpd in chroot (--secure) mode:
#
config boot
option filename 'pxelinux.0'
option serveraddress '192.168.1.77'
option servername 'pizza'
</pre>
<p>Restart dnsmasq:</p>
<pre class="literal-block">
/etc/init.d/dnsmasq restart
</pre>
</div>
<div class="section" id="tftpd-on-pizza">
<h3>tftpd on pizza</h3>
<p>I'm using <tt class="docutils literal"><span class="pre">openbsd-inetd</span></tt> as my inetd server and running tftpd from inetd
since I do not need to run it all the time. I configured tftpd-hpa on a server
named <strong>pizza</strong> on my LAN.</p>
<p>Install and prepare tftpd</p>
<pre class="literal-block">
sudo apt-get install tftpd-hpa
sudo mkdir -p /srv/tftp
</pre>
<p>Do not run as a standalone server:</p>
<pre class="literal-block">
/etc/init.d/tftpd-hpa stop
rm /etc/init.d/S03tftpd-hpa
</pre>
<p>Configure inetd to run tftpd. Add this to <tt class="docutils literal">/etc/inetd.conf</tt>:</p>
<pre class="literal-block">
#
# We *might* want to change --timeout (default 900 or 15 minutes), which
# is the timeout before the server will run after a connection is received before
# it terminates.
#
# -s or --secure (chroot on startup)
#
# -u tftp is USER that the daemon will run as (default is nobody).
# Installing the tftpd-hpa package creates a tftp user
#
tftp dgram udp wait root /usr/sbin/in.tftpd /usr/sbin/in.tftpd -u tftp --secure /srv/tftp
</pre>
<p>Restart inetd:</p>
<pre class="literal-block">
/etc/init.d/openbsd-inetd restart
</pre>
<p>Download the debian netboot image, and extract to <tt class="docutils literal">/srv/tftp</tt>. We should see:</p>
<pre class="literal-block">
ls -1 /srv/tftp/
debian-installer
pxelinux.0
pxelinux.cfg
version.info
</pre>
</div>
<div class="section" id="add-options-to-boot-systemrescuecd-with-pxe">
<h3>add options to boot SystemRescueCD with PXE</h3>
<p>The debian installer netboot tarball variant does not include fdisk. I needed
it. I also wanted to use ntfsclone to copy my NTFS partition. Therefore,
I also downloaded SystemRescueCD to transfer my windows XP partition before
proceeding with the debian installation.</p>
<p><em>NOTE</em>: SystemRescueCD has become bloated. I've used it in the past, and years
ago it used to be about 100MB. It now includes xorg and a bunch of GUI tools,
and the size of the image is over 300MB. This is fine when booting from
physical storage (USB drive or optical media), but it's slow to transfer an
image this size over a LAN. I used SystemRescueCD to transfer the windows XP
partition from my old hard drive to the new one during my installation, but
next time I'll try <a class="reference external" href="http://rescuecd.pld-linux.org/">the PLD rescue cd</a>
instead.</p>
<p>I downloaded it, copied the iso to my server, mounted it as a loopback,
and copied the contents to <tt class="docutils literal"><span class="pre">/srv/tftp/system-rescue-cd/system-rescue-cd-2.4.0/</span></tt>:</p>
<pre class="literal-block">
### Do all this stuff as root
mkdir -p /mnt/tmp
mkdir -p /srv/tftp/system-rescue-cd/system-rescue-cd-2.4.0
cd /srv/tftp/system-rescue-cd
ln -s system-rescue-cd-2.4.0 current
cd current
mount -o loop -tiso9660 /dev/shm/systemrescuecd-x86-2.4.0.iso /mnt/tmp
cp -a /mnt/tmp/* .
</pre>
<p>Then I made an entry for systemrescuecd in my PXE boot configuration, which
I put in <tt class="docutils literal">/srv/tftp/sysrescue32.cfg</tt>:</p>
<pre class="literal-block">
label sysrescue32
menu label ^sysrescue32
kernel system-rescue-cd/current/isolinux/rescuecd
append vga=788 initrd=system-rescue-cd/current/isolinux/initram.igz
</pre>
<p>Then I added a line for that config file to <tt class="docutils literal">/srv/tftp/pxelinux.cfg/default</tt>:</p>
<pre class="literal-block">
# D-I config version 2.0
include debian-installer/i386/boot-screens/menu.cfg
### This is the line I added to the default config:
include sysrescue32.cfg
default debian-installer/i386/boot-screens/vesamenu.c32
prompt 0
timeout 0
</pre>
</div>
<div class="section" id="imaging-and-installation">
<h3>imaging and installation</h3>
<p>I'm not going to cover the actual debian installation in detail. Once I had
openwrt's dnsmasq DHCP set up to point to the tftpd running via inetd on pizza,
all I had to do to PXE boot was hit F12 when I booted the old laptop to select PXE
as the boot option.</p>
<p>Then I was greeted with a debian splash screen, from which I could select either one
of the debian boot options or my <tt class="docutils literal">sysrescue32</tt> SystemRescueCD boot option.</p>
<p>To complete my installation, I did (roughly):</p>
<blockquote>
<ul class="simple">
<li>booted into SystemRescueCD, mounted my old hard drive with a USB enclosure,</li>
<li>created a single NTFS partition on the new drive with fdisk</li>
<li>imaged the old NTFS partition over the new partition with ntfsclone</li>
<li>created a single VFAT (FAT32 LBA type 0x0C) partition with fdisk to use
for shared data between windows XP and linux</li>
<li>rebooted into the debian installer and installed debian (installing GRUB2
to the MBR)</li>
</ul>
</blockquote>
</div>
</div>
</div>greghttp://www.blogger.com/profile/10634561928684519035noreply@blogger.com0tag:blogger.com,1999:blog-2341686445392356400.post-33061615525349069622011-12-13T18:39:00.001-05:002011-12-13T18:39:38.504-05:00readthedocs.org<div class="section" id="readthedocs-org">
<p>I just discovered <a class="reference external" href="http://readthedocs.org">readthedocs.org</a> today, and it's
awesome! The organization behind the site will build and host documentation for
any open source project. It works with any projects that use <a class="reference external" href="http://sphinx.pocoo.org/">sphinx</a>/<a class="reference external" href="http://docutils.sourceforge.net/">reStructuredText</a> for their documentation.</p>
<p>I'm a fan of reST (reStructuredText). Its semantic processing of whitespace can be
confusing when you're first starting out, and the format used for tables can
be cumbersome if your tables are complex, but you get used to it.</p>
<p>I write these blog posts in reST, I write notes in reST, and recently I started
using reST + sphinx to document <a class="reference external" href="https://github.com/pajenieta/handset_tester">an open source project</a>.</p>
<p>I considered using github's wiki feature for project documentation, but after
reading about it, I decided not to use it. As of this writing, it does not
support auto-building documentation in HTML format from markup formats
defined in your main project source tree. You can use git for your
project wiki/docs, but github will create a separate git repo for the
documentation.</p>
<p>With sphinx, you can take your reST sources and build documentation in
multiple formats. readthedocs.org supports html, epub, and pdf out of
the box!</p>
<p>readthedocs.org uses a build system that scans your project source tree to find
the conf.py at the root of your sphinx documentation. My project had the sphinx
docs rooted in <tt class="docutils literal">src/site/sphinx</tt>, with the <tt class="docutils literal">conf.py</tt> in the <tt class="docutils literal">source</tt>
subdirectory, and ReadTheDocs was able to find this and build my documentation
without requiring me to specify the paths. All I had to do was:</p>
<blockquote>
<ul class="simple">
<li>sign up for an account on readthedocs.org</li>
<li>configure a new project on ReadTheDocs and specify the read-only-access
url of my github project in the ReadTheDocs project configuration form.</li>
</ul>
</blockquote>
<p>And that was it! ReadTheDocs checked out my project source, built the
documentation in html format, and <a class="reference external" href="http://handset_tester.readthedocs.org/en/latest/index.html">published it here</a>.</p>
<p>If you customize your sphinx layout, you have to contact the ReadTheDocs team
to whitelist your custom configuration (this may be automated in the future).
Otherwise, ReadTheDocs will build your documentation with a some default sphinx
settings.</p>
<p>ReadTheDocs also provides a unique web service endpoint that you can call to
rebuild your documentation. Github provides a custom post-commit hook for
ReadTheDocs that can be configured on your github project page by navigating
to:</p>
<pre class="literal-block">
admin -> service hooks -> ReadTheDocs
</pre>
<p>Once you do that, your documentation will be rebuilt automatically every time
you push code to master. Otherwise, by default, the documentation will be
rebuilt nightly.</p>
</div>greghttp://www.blogger.com/profile/10634561928684519035noreply@blogger.com0tag:blogger.com,1999:blog-2341686445392356400.post-87708564495192810012011-12-06T04:44:00.000-05:002011-12-06T10:01:52.158-05:00anyremote j2me client<div class="section" id="anyremote-j2me-client">
<p>I purchased a couple low-cost, used j2me-capable phones on eBay recently for a
j2me development project. While watching a movie with my wife on our desktop
computer the other day (we do not have a TV) and getting up repeatedly from the
couch to walk over to the desktop to adjust the volume, I got the idea to write
a simple j2me MIDlet that could use a phone's bluetooth interface to function
as a remote control for the desktop.</p>
<p>It turns out that there is already an open source project for that called
<a class="reference external" href="http://anyremote.sourceforge.net/">anyRemote</a>. There are packages
for it in debian stable:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre>sudo apt-get install anyremote
</pre></div>
</td></tr></table><p>I know that one of my phones, a Nokia 5130-c2 with T-Mobile firmware,
will throw a <tt class="docutils literal">SecurityException</tt> if an unsigned MIDlet tries to access
any APIs that require permissions (including bluetooth). The GNU autotools-based
build for anyremote-j2me-client does not include options to sign your MIDlet.
Since I already wrote a portable ant-based build system for another j2me
MIDlet that includes a step to sign a jad and also ensures that jar MANIFEST
and jad metadata are consistent, I ported my build scripts to the anyremote
j2me client.</p>
<p>I <a class="reference external" href="https://github.com/gorlowski/anyremote-j2me-client">published my build of the anyremote-j2me-client on github</a>.</p>
<p>If you will be running the anyremote j2me client on a Nokia and need to sign
your MIDlet, check out my <a class="reference external" href="http://windowputty.blogspot.com/2011/12/installing-code-signing-certificates-on.html">previous blog post on installing self-signed
code-signing certificates on nokia s40 handsets</a>.</p>
<p>It took a little trial and error to get the client working with VLC. I'm
pasting my notes below.</p>
<div class="section" id="vlc-with-anyremote">
<h2>vlc with anyremote</h2>
<p>I tried the ganyremote gtk client for anyremote, but I didn't have any luck
with it. Having said that, I spent no more than a minute trying to get it
to work with VLC, and I did not RTFM at all.</p>
<p>I figured out how to use the console-based anyremote server after a quick
scan of the manpage, and that's what I document below.</p>
<p>First we need to configure VLC to play a media file, and run an embedded HTTP
server on host:port localhost:8080 to accept commands from remote clients:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre>vlc -I http --http-host localhost:8080 mymovie.avi
</pre></div>
</td></tr></table><p>Then we need to configure the anyremote server to listen on our bluetooth
interface, using a configuration customized for remote VLC control. In the
following example, the anyremote server listens on bluetooth channel 19:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre>anyremote -s bluetooth:19 -f /usr/share/anyremote/cfg-data/Server-mode/vlc.cfg
</pre></div>
</td></tr></table><p>To find a list of cfg files installed with anyremote, run:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre>dpkg -L anyremote-data |grep cfg
</pre></div>
</td></tr></table><p>Down the road, I will play around with customizing the config file and store
my modified file in some subdirectory of $HOME.</p>
<p>As root, we need to make our bluetooth adapter visible to external bluetooth
client scans:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre>sudo hciconfig hci0 piscan
</pre></div>
</td></tr></table><p>Once the anyremote server is running and our hci0 interface allows remote clients
to scan the channels, the anyremote-j2me-client can <tt class="docutils literal">search</tt> for peers, find
the anyremote server, and then connect to the service.</p>
<p>Then the remote control interface will be launched on the client, and we can
pause, stop, fast forward, rewind, and adjust volume.</p>
</div>
<div class="section" id="anyremote-j2me-client-build-for-nokia-5130c2">
<h2>anyremote-j2me-client build for nokia 5130c2</h2>
<p>My Nokia 5130 has a 240x320 resolution. The default vlc configuration for
anyremote uses 4-rows of buttons. I found that the 48-pixel icon set is the
best size for the nokia screen. I'm guessing that, for any given j2me device,
you should calculate:</p>
<pre class="literal-block">
icon_size_max = vertical_resolution / 6
</pre>
<p>And then choose the larges available icon size that is < icon_size_max. For me
that is 48-pixels, which means that I built my <a class="reference external" href="https://github.com/gorlowski/anyremote-j2me-client">anyremote-j2me-client</a> using:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre>ant -Dicon.size<span class="o">=</span>48 -Dsign.app<span class="o">=</span><span class="nb">true </span>clean package
</pre></div>
</td></tr></table></div>
</div>
greghttp://www.blogger.com/profile/10634561928684519035noreply@blogger.com0tag:blogger.com,1999:blog-2341686445392356400.post-38451024901015707742011-12-02T14:37:00.000-05:002011-12-06T10:12:11.669-05:00installing code signing certificates on j2me phones<div class="section" id="installing-code-signing-certificates-on-j2me-phones">
<p>Recently, I got a couple j2me-capable phones on ebay as test devices for some
development work that I'm doing for an NGO based in West Africa. I'm developing
a literacy-education tool that is intended to be deployed on low-cost j2me
devices because this is the technology platform with the greatest market
penetration in the area. Most people in the region <em>do not</em> have laptops,
desktops, OLPCs, tablets, android phones or iPhones. Many people <em>do</em> have
low-cost j2me-capable phones manufactured by nokia, samsung, LG, etc.</p>
<p>To test my j2me code on real hardware (not just emulators), I purchased a couple
second-hand mobile phones on eBay.</p>
<p>I do not use this type of device day to day. I have a much fancier android
phone that operates on a CDMA network. My test devices are gsm.</p>
<p>One is a samsung C3050 and the other is a nokia 5130 XM. Both are relatively
cheap devices. My C3050 isn't totally locked-down/crippled because it does not
have a T-Mobile firmware. The guy who sold it to me on eBay shipped it from NY,
and he left an old T-Mobile USA SIM card in the phone, but the firmware version
code indicates that it was originally sold by "China Mobile Communications
Corporation".</p>
<p>The nokia came with a T-Mobile USA firmware. On nokia
s40 phones (and maybe other j2me phones), T-Mobile modifies the core
manufacturer firmwares to disallow running apps that would otherwise run in the
"trusted third party" j2me security domain. The phone contains Thawte and
VeriSign root x509 certificates, but T-Mo does not allow you to run apps signed
with code-signing certs. I learned this from <a class="reference external" href="http://www.developer.nokia.com/Community/Wiki/T-Mobile_U.S._Java_security_domains">developer.nokia.com</a>.</p>
<p>In any case, (many? some?) nokia s40 phones do not allow you to install your own
certificates for code signing. If you do a web search, you will find
<a class="reference external" href="http://javablog.co.uk/2007/08/09/how-midlet-signing-is-killing-j2me/">several</a>
<a class="reference external" href="http://www.ayefon.com/j2mesecrets/page3.cfm">other blogs</a> lamenting the
developer-unfriendly "<em>security model</em>" (in scare quotes because it has more
to do with securing revenue than with device security) on j2me mobile devices.</p>
<p>It took way too much binary diffing, staring at hex dumps, and trial + error,
but I reverse engineered enough of the file format of nokia's internal binary
cert DB file to figure out how to install a self signed cert that runs in what
I believe is the operator protection domain. The partial analysis of the nokia
ext_info.sys file on <a class="reference external" href="http://www.tzell.mynetcologne.de/cert.html">this page by Thomas Zell</a> helped a lot.</p>
<p>I created a <a class="reference external" href="https://github.com/gorlowski/dustbowl">github project called dustbowl</a>
to alter ext_info.sys files.</p>
<p>I also posted <a class="reference external" href="http://sites.google.com/site/gorlowskipub/nokia-s40-self-signed-certificates">this analysis of the ext_info.sys file format</a>.</p>
<dl class="docutils">
<dt>Here are some really great tools that enabled my analysis:</dt>
<dd><dl class="first last docutils">
<dt>gammu:</dt>
<dd>This is a fantastic project that implements the nokia FBUS protocol
to support accessing the filesystem on supported nokia phones.</dd>
<dt>vbindiff:</dt>
<dd>This is a nice, lightweight console-based binary diff program</dd>
<dt>hexdump:</dt>
<dd>a good tool for a quick traditional hex-editor formatted view of
a binary file.</dd>
</dl>
</dd>
</dl>
<div class="section" id="update">
<h2>update</h2>
<p>Since I wrote this, I found an open source project called <a class="reference external" href="http://code.google.com/p/nokicert/">nokicert</a> that also supports installing
code-signing certificates on nokia s40 phones. I was able to build and run it,
but I haven't tried to use it to install certs on my own phone. I confirmed
that the feature to read the cert DB on my phone works fine.</p>
<p>Nokicert installs certs to your phone's <em>auth</em> certificate DB not to the <em>user</em>
certificate DB. On my firmware/device (T-Mobile USA/nokia 5130c2), it is
sufficient to install certs to the <em>user</em> certificate DB, and doing so incurs
less risk of possibly bricking your phone.</p>
<p>I reached out to Francois Kooman, the developer of nokicert, and he graciously
shared his notes on reverse engineering the security model of nokia phones. He
managed to figure out several things about the security model that I had not
figured out from my tinkering.</p>
</div>
</div>
greghttp://www.blogger.com/profile/10634561928684519035noreply@blogger.com0tag:blogger.com,1999:blog-2341686445392356400.post-691697185236904242011-09-19T23:39:00.001-04:002011-09-19T23:39:21.880-04:00First Look at Querydsl with JPA 2<div class="section" id="initial-exposure-to-querydsl-with-jpa-2">
<p>Today I integrated integrated <a class="reference external" href="http://www.querydsl.com/">Querydsl</a>
into a java webapp that I recently started coding.</p>
<p>The project was already using JPA 2 (Hibernate). I used JPQL to implement
an initial set of finder methods for some simple use-cases, but I decided
to explore alternatives to JPQL when I reached a use-case that required
me to build a query dynamically based on a search form that contains
about half a dozen search fields.</p>
<p>In the past, before JPA 2, I used to use Hibernate's proprietary
criteria API in these cases, and my search methods would dynamically
build a criteria with code like this:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7
8</pre></div></td><td class="code"><div class="highlight"><pre><span class="kd">public</span> <span class="n">List</span><span class="o"><</span><span class="n">User</span><span class="o">></span> <span class="n">findAll</span><span class="o">(</span><span class="n">UserSearchForm</span> <span class="n">userSearchForm</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Criteria</span> <span class="n">criteria</span> <span class="o">=</span> <span class="n">getSession</span><span class="o">().</span><span class="na">createCriteria</span><span class="o">(</span><span class="n">User</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="k">if</span><span class="o">(</span> <span class="n">userSearchForm</span><span class="o">.</span><span class="na">getLastName</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">)</span> <span class="o">{</span>
<span class="n">criteria</span><span class="o">.</span><span class="na">add</span><span class="o">(</span> <span class="n">Restrictions</span><span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="s">"lastName"</span><span class="o">,</span> <span class="n">userSearchForm</span><span class="o">.</span><span class="na">getLastName</span><span class="o">())</span> <span class="o">);</span>
<span class="o">}</span>
<span class="c1">// ... more conditional checks + more restrictions</span>
<span class="k">return</span> <span class="n">criteria</span><span class="o">.</span><span class="na">list</span><span class="o">();</span>
<span class="o">}</span>
</pre></div>
</td></tr></table><p>JPA 2 has a criteria API, and I spent a little time today reading documentation
to learn the new API. After reading it over for a while, I concluded that I
don't think the JPA 2 criteria API is productive for human consumption.</p>
<p>If I were to implement the above sample code with the JPA 2 criteria API, I
believe it would read something like this:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28</pre></div></td><td class="code"><div class="highlight"><pre><span class="c1">// NOTE: I did not test this code. This is an example typed into my text editor</span>
<span class="c1">// freehand.</span>
<span class="kd">public</span> <span class="n">List</span><span class="o"><</span><span class="n">User</span><span class="o">></span> <span class="n">findAll</span><span class="o">(</span><span class="n">UserSearchForm</span> <span class="n">userSearchForm</span><span class="o">)</span> <span class="o">{</span>
<span class="n">CriteriaBuilder</span> <span class="n">cb</span> <span class="o">=</span> <span class="n">em</span><span class="o">().</span><span class="na">getCriteriaBuilder</span><span class="o">;</span> <span class="c1">// CriteriaBuilder: class #1</span>
<span class="n">CriteriaQuery</span><span class="o"><</span><span class="n">User</span><span class="o">></span> <span class="n">cq</span> <span class="o">=</span> <span class="n">cb</span><span class="o">.</span><span class="na">createQuery</span><span class="o">(</span><span class="n">User</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="c1">// CriteriaQuery: class #2</span>
<span class="n">Root</span><span class="o"><</span><span class="n">User</span><span class="o">></span> <span class="n">user</span> <span class="o">=</span> <span class="n">cq</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="n">User</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="c1">// Root: class #3</span>
<span class="n">List</span><span class="o"><</span><span class="n">Predicate</span><span class="o">></span> <span class="n">allCriteria</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">Predicate</span><span class="o">>();</span> <span class="c1">// Predicate: class #4</span>
<span class="k">if</span> <span class="o">(</span><span class="n">userSearchForm</span><span class="o">.</span><span class="na">getLastName</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// ParameterExpression: class #5</span>
<span class="n">ParameterExpression</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">px</span> <span class="o">=</span> <span class="n">cb</span><span class="o">.</span><span class="na">parameter</span><span class="o">(</span><span class="n">String</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="s">"lastName"</span><span class="o">);</span>
<span class="n">allCriteria</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">cb</span><span class="o">.</span><span class="na">equal</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"lastName"</span><span class="o">),</span> <span class="n">px</span><span class="o">));</span>
<span class="o">}</span>
<span class="c1">// ... more</span>
<span class="c1">// no error checking for 0 case</span>
<span class="k">if</span><span class="o">(</span><span class="n">allCriteria</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">==</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
<span class="n">cq</span><span class="o">.</span><span class="na">where</span><span class="o">(</span><span class="n">allCriteria</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">));</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">cq</span><span class="o">.</span><span class="na">where</span><span class="o">(</span><span class="n">cb</span><span class="o">.</span><span class="na">and</span><span class="o">(</span> <span class="n">allCriteria</span><span class="o">.</span><span class="na">toArray</span><span class="o">(</span><span class="k">new</span> <span class="n">Predicate</span><span class="o">[</span><span class="n">allCriteria</span><span class="o">.</span><span class="na">size</span><span class="o">()])));</span>
<span class="o">}</span>
<span class="n">TypedQuery</span><span class="o"><</span><span class="n">User</span><span class="o">></span> <span class="n">q</span> <span class="o">=</span> <span class="n">em</span><span class="o">.</span><span class="na">createQuery</span><span class="o">(</span><span class="n">cq</span><span class="o">);</span> <span class="c1">// TypedQuery: class #7</span>
<span class="k">if</span><span class="o">(</span> <span class="n">userSearchForm</span><span class="o">.</span><span class="na">getLastName</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">)</span>
<span class="n">q</span><span class="o">.</span><span class="na">setParameter</span><span class="o">(</span><span class="s">"lastName"</span><span class="o">,</span> <span class="n">userSearchForm</span><span class="o">.</span><span class="na">getLastName</span><span class="o">());</span>
<span class="c1">// ... more</span>
<span class="k">return</span> <span class="n">q</span><span class="o">.</span><span class="na">getResultList</span><span class="o">();</span>
<span class="o">}</span>
</pre></div>
</td></tr></table><p>The JPA 2 criteria API also supports using the <em>canonical metamodel</em> to create
criteria queries with type-safe component expressions. I'm not going to provide
an example here because the major classes used by the typesafe API are the same
(CriteriaQuery, Root, CriteriaBuilder, etc), and because examples are easy
enough to find with a <a class="reference external" href="http://duckduckgo.com/?q=jpa2%20canonical%20metamodel%20type%20safe%20criteria">simple web search</a>.</p>
<p>While the API is very flexible, to me the component-classes feel like AST node
classes designed to be written and read by a machine interpreter rather than a
human. IMHO, it fails the StringBuilder litmus test. If I rewrote it to
dynamically build a JPQL query using a StringBuilder, I believe it would be
shorter and more readable. When APIs designed to build expressions require
the use of many classes and verbose statements even for simple cases, the
resultant code is hard to read. Code that is hard to read is hard to debug,
and code that is hard to debug is more likely to contain bugs (it also
takes longer to write, longer to test, etc.) Basically, this API is screaming
for a human-friendly DSL facade.</p>
<p>Enter Querydsl. The mysema blog <a class="reference external" href="http://blog.mysema.com/2010/04/querydsl-as-alternative-to-jpa-2.html">alread has a nice comparison of JPA 2 Criteria
and Querydsl queries</a>,
but I'll briefly write an example of how the above code would look in Querydsl:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10</pre></div></td><td class="code"><div class="highlight"><pre><span class="c1">// again, this is not tested. let me know if you spot an error</span>
<span class="kd">public</span> <span class="n">List</span><span class="o"><</span><span class="n">User</span><span class="o">></span> <span class="n">findAll</span><span class="o">(</span><span class="n">UserSearchForm</span> <span class="n">userSearchForm</span><span class="o">)</span> <span class="o">{</span>
<span class="n">QUser</span> <span class="n">user</span> <span class="o">=</span> <span class="n">QUser</span><span class="o">.</span><span class="na">user</span><span class="o">;</span>
<span class="n">JPQLQuery</span> <span class="n">query</span> <span class="o">=</span> <span class="n">queryFrom</span><span class="o">(</span><span class="n">user</span><span class="o">);</span> <span class="c1">// queryFrom is just new JPAQuery(em()).from(user)</span>
<span class="k">if</span> <span class="o">(</span><span class="n">userSearchForm</span><span class="o">.</span><span class="na">getLastName</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">query</span><span class="o">.</span><span class="na">where</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">lastName</span><span class="o">.</span><span class="na">eq</span><span class="o">(</span><span class="n">userSearchForm</span><span class="o">.</span><span class="na">getLastName</span><span class="o">()));</span>
<span class="o">}</span>
<span class="c1">// ... apply more restrictions conditionally</span>
<span class="k">return</span> <span class="n">query</span><span class="o">.</span><span class="na">list</span><span class="o">(</span><span class="n">user</span><span class="o">);</span>
<span class="o">}</span>
</pre></div>
</td></tr></table><p>Note that, while Querydsl uses a <a class="reference external" href="http://www.martinfowler.com/bliki/FluentInterface.html">method-chaining fluent interface</a>, most (all?) methods
that can be invoked on Querydsl's JPQLQuery will mutate the query object
(<em>i.e.</em>, you do not need to write <tt class="docutils literal">query = query.where(predicateA); query =
query.where(predicateB); ...</tt> when building a query with multiple statements)
. So we can chain together all the method calls into a compound statement, or
we can build a query object with multiple statements. This option gives us
terse, simple code for simple cases while still supporting more complex cases
that require building a query dynamically based on a series of conditions.</p>
<!-- I'll go off on an aside here to share some opinions about APIs, type safety, -->
<!-- and productivity. Among programmers, there is an ideological divide over the -->
<!-- merits of compile-time type checking (static typing). In one camp, you have the -->
<!-- java/C#/C++ users who want their compilers to do as much type checking as -->
<!-- possible at compile time, which allows them to work in an IDE that provides -->
<!-- statement completion and instant feedback when there are syntax errors. In the -->
<!-- other camp, you have the perl/python/ruby fanboys who argue that static typing -->
<!-- results in verbose, inflexible APIs with relatively less code reusability. My -->
<!-- tastes fall somewhere between these two poles, and here I believe I'm like most -->
<!-- professional code slingers. I'm all for rich, static typing up until the point -->
<!-- where an API has so -->
<p>Well, this blog post has more to do with my reaction toward the JPA 2 Criteria
API than it has to do with Querydsl. Tomorrow, I'll write a post about
some gotchas that I ran into with Querydsl + JPA.</p>
<!-- originall on the fence re: Querydsl (limit on referencing secondary associations) -->
<!-- @QueryInit annotation did not work for me -->
<!-- I like explicit joins are better anyway (they make it more clear how much -->
<!-- work the DB engine will have to do to execute the query while still abstracting -->
<!-- away the specific SQL dialect + JPA implementation. -->
</div>
greghttp://www.blogger.com/profile/10634561928684519035noreply@blogger.com0tag:blogger.com,1999:blog-2341686445392356400.post-40647629571207066702011-05-23T10:34:00.000-04:002011-05-24T00:32:36.413-04:00gnu screen xwindows clipboard integration<!-- gnu screen xwindows clipboard integration -->
<p>Part of the reason that I'm writing this blog post is to make sure I
properly configured sourcecode syntax highlighting for my blog.</p>
<p>I created <a class="reference external" href="https://github.com/gorlowski/reStructuredText-Blog-Authoring-Tools">a project with utilities
for authoring blog posts in reStructuredText syntax</a> so I
can write my blog articles in reStructuredText syntax using vim, preview as
html with a simple vim command, and translate to html for publication.</p>
<p>Not too long ago, I tinkered with a very simple program to integrate the gnu
screen buffer and xwindows CLIPBOARD. I ended up rewriting it in lua, perl and
c as an experiment to see the relative performance differences. I pasted
the 3 versions below to verify that syntax highlighting looks good with
all 3 languages.</p>
<p>There were other examples of screen buffer <-> xwindows clipboard integration
on the net, but none of them worked for me. To get it working, I had to add a
10ms sleep after copying /tmp/screen_exchange to my xwindows clipboard with
<tt class="docutils literal">xsel <span class="pre">-bi</span></tt>. I'm not sure why the sleep is necessary, but I could not get it
to work consistently without at least a 10ms sleep. Eventually I will peek
into the screen and xsel code to better understand the apparent race condition,
but for now working around the problem with a 10ms sleep is fine.</p>
<p>So now I can enter <tt class="docutils literal">Copy Mode</tt> in screen, mark the start of my selection as
usual with the space bar, and press <tt class="docutils literal">.</tt> to close the selection. The final
<tt class="docutils literal">.</tt> exits <tt class="docutils literal">Copy Mode</tt> and copies the screen buffer to my xwindows clipboard
with a single keypress.</p>
<p>For reference, here is my ~/.screenrc:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22</pre></div></td><td class="code"><div class="highlight"><pre>startup_message off
hardstatus alwayslastline
### Support for "tabs" in the screen status line:
hardstatus string '%{= kG}[ %H ][ %{= kw}%-w%{= bk}%n*%t%{= kw}%+w %= %{g}][%{B} %{W}%c %{g}]'
shelltitle " "
### In copy mode, map '.' to copy the selection to the Xwindows clipboard:
# explanation:
#
# stuff ' ' -- this enters a space character in your terminal,
# effectively ending Copy Mode and putting your
# selection in the screen paste buffer
#
# writebuf -- Writes the screen paste buffer out to a file
# (default is /tmp/screen-exchange )
#
# exec 'screen_buff_copy' -- executes screen_buff_copy, which is a program
# that will read /tmp/screen_buff_copy and write
# it to the Xwindows CLIPBOARD with xsel -bi
#
bindkey -m . eval "stuff ' '" "writebuf" "exec '/home/greg/scripts/screen/screen_buff_copy'"
</pre></div>
</td></tr></table><p>During my initial troubleshooting, I decided to put the <tt class="docutils literal">screen_exchange <span class="pre">-></span>
xsel</tt> step into an external screen_buff_copy program so I could invoke
the program outside of screen to minimize the moving parts. Once I discovered
the sleep workaround, I decided to try to make my screen_buff_copy program as lightweight
as possible because it executes every time I want to copy text to my clipboard.
I settled on lua, perl and c for my implementation trials because they have
less startup overhead for short programs than, e.g., java or python.</p>
<p>I first wrote the program in lua:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24</pre></div></td><td class="code"><div class="highlight"><pre><span class="cp">#!/usr/bin/lua</span>
<span class="c1">-- NOTE: the socket does not ship as part of the lua std libs</span>
<span class="c1">-- You have to install it separately.</span>
<span class="nb">require</span><span class="p">(</span><span class="s1">'</span><span class="s">io'</span><span class="p">)</span>
<span class="nb">require</span><span class="p">(</span><span class="s1">'</span><span class="s">socket'</span><span class="p">)</span>
<span class="n">pipe</span> <span class="o">=</span> <span class="nb">io.popen</span><span class="p">(</span><span class="s2">"</span><span class="s">/usr/bin/xsel -bi"</span><span class="p">,</span> <span class="s2">"</span><span class="s">w"</span><span class="p">)</span>
<span class="n">fp</span> <span class="o">=</span> <span class="nb">io.open</span><span class="p">(</span><span class="s2">"</span><span class="s">/tmp/screen-exchange"</span><span class="p">)</span>
<span class="c1">-- write file contents to pipe</span>
<span class="n">pipe</span><span class="p">:</span><span class="n">write</span><span class="p">(</span><span class="n">fp</span><span class="p">:</span><span class="n">read</span><span class="p">(</span><span class="s2">"</span><span class="s">*all"</span><span class="p">))</span>
<span class="c1">-- close pipe and file pointer</span>
<span class="n">pipe</span><span class="p">:</span><span class="n">close</span><span class="p">()</span>
<span class="n">fp</span><span class="p">:</span><span class="n">close</span><span class="p">()</span>
<span class="c1">-- There is some race condition in the entire screen slurping/exec/whatever</span>
<span class="c1">-- process. Without a sleep, it does not work consistently. There is also a lag</span>
<span class="c1">-- of about 1 second in the stuff, writebuf and exec steps before this script is</span>
<span class="c1">-- properly executed and the time that the clipboard is actually set</span>
<span class="c1">-- sleep for 0.01 seconds:</span>
<span class="n">socket</span><span class="p">.</span><span class="n">select</span><span class="p">(</span><span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">,</span> <span class="mf">0.01</span><span class="p">)</span>
</pre></div>
</td></tr></table><p>I was not thrilled that I had to load a library that is not part of the std libs
(socket), and I quickly rewrote it in perl, curious to see perl's relative performance.</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20</pre></div></td><td class="code"><div class="highlight"><pre><span class="c1">#!/usr/bin/perl</span>
<span class="c1">#</span>
<span class="c1"># This is very very marginally more lightweight than the lua version,</span>
<span class="c1"># and one nice thing about it is that it uses all perl builtins without</span>
<span class="c1"># requiring external libs (in lua's case, the external socket lib is required</span>
<span class="c1"># for sleep)</span>
<span class="c1">#</span>
<span class="nb">open</span><span class="p">(</span><span class="n">F</span><span class="p">,</span> <span class="s">"/tmp/screen-exchange"</span><span class="p">);</span>
<span class="nb">open</span><span class="p">(</span><span class="n">P</span><span class="p">,</span> <span class="s">"| xsel -bi"</span><span class="p">);</span>
<span class="k">print</span> <span class="n">P</span> <span class="s-Regexp"><F></span><span class="p">;</span>
<span class="nb">close</span> <span class="n">F</span><span class="p">;</span>
<span class="nb">close</span> <span class="n">P</span><span class="p">;</span>
<span class="c1">### Sleep (needed due to weird race condition)</span>
<span class="k">my</span> <span class="nv">$sleep_seconds</span> <span class="o">=</span> <span class="mi">0</span><span class="o">.</span><span class="mo">01</span><span class="p">;</span>
<span class="nb">select</span><span class="p">(</span><span class="nb">undef</span><span class="p">,</span> <span class="nb">undef</span><span class="p">,</span> <span class="nb">undef</span><span class="p">,</span> <span class="nv">$sleep_seconds</span> <span class="p">);</span>
</pre></div>
</td></tr></table><p>And then I decided to write it in C just to see if the overhead was perceptibly
lower. Interestingly, it really was not. The C version is not faster than the
perl version, but it requires about twice as many lines of code.</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41</pre></div></td><td class="code"><div class="highlight"><pre><span class="cp">#include <stdio.h></span>
<span class="cp">#include <unistd.h> </span><span class="cm">/* for usleep */</span><span class="cp"></span>
<span class="cm">/*************************************************************</span>
<span class="cm">*</span>
<span class="cm">* screen_buff_copy.c</span>
<span class="cm">*</span>
<span class="cm">* It is totally unnecessary to do this in C. The performance</span>
<span class="cm">* is almost indistinguishable from the perl version.</span>
<span class="cm">*</span>
<span class="cm">*************************************************************/</span>
<span class="cm">/* Sleep for 10 milliseconds (seems like the min needed for reliability) */</span>
<span class="cp">#define MILLISECOND_IN_USEC 1000</span>
<span class="cp">#define USLEEP_TIME MILLISECOND_IN_USEC * 10</span>
<span class="cp">#define BUFF_SIZE 80</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">FILE</span> <span class="o">*</span><span class="n">pipe</span><span class="p">;</span>
<span class="kt">FILE</span> <span class="o">*</span><span class="n">fp</span><span class="p">;</span>
<span class="kt">size_t</span> <span class="n">num_read</span><span class="p">;</span> <span class="cm">/* number of items read */</span>
<span class="kt">char</span> <span class="n">buff</span><span class="p">[</span><span class="n">BUFF_SIZE</span><span class="p">];</span>
<span class="n">pipe</span> <span class="o">=</span> <span class="n">popen</span><span class="p">(</span><span class="s">"/usr/bin/xsel -bi"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">);</span>
<span class="n">fp</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"/tmp/screen-exchange"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span>
<span class="k">while</span><span class="p">(</span> <span class="p">(</span><span class="n">num_read</span> <span class="o">=</span> <span class="n">fread</span><span class="p">(</span><span class="n">buff</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">char</span><span class="p">),</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buff</span><span class="p">),</span> <span class="n">fp</span><span class="p">))</span> <span class="o">></span> <span class="mi">0</span> <span class="p">)</span> <span class="p">{</span>
<span class="n">fwrite</span><span class="p">(</span><span class="n">buff</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">char</span><span class="p">),</span> <span class="n">num_read</span><span class="p">,</span> <span class="n">pipe</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">fflush</span><span class="p">(</span><span class="n">pipe</span><span class="p">);</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">fp</span><span class="p">);</span>
<span class="n">pclose</span><span class="p">(</span><span class="n">pipe</span><span class="p">);</span>
<span class="cm">/* Yes, we need to sleep */</span>
<span class="n">usleep</span><span class="p">(</span><span class="n">USLEEP_TIME</span><span class="p">);</span>
<span class="k">return</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
</td></tr></table><p>A simple benchmark program (another opportunity for syntax higlighting another lang):</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10</pre></div></td><td class="code"><div class="highlight"><pre><span class="c">#!/bin/sh</span>
<span class="nv">ITER</span><span class="o">=</span>100
<span class="nv">sbc</span><span class="o">=</span>screen_buff_copy
<span class="k">for </span>p in ./<span class="nv">$sbc</span> ./<span class="k">${</span><span class="nv">sbc</span><span class="k">}</span>.pl ./<span class="k">${</span><span class="nv">sbc</span><span class="k">}</span>.lua; <span class="k">do</span>
<span class="k"> </span><span class="nb">echo</span> <span class="s2">"Timing $ITER iterations of ${p}: "</span>
<span class="nb">time </span><span class="k">for </span>n in <span class="sb">`</span>seq <span class="nv">$ITER</span><span class="sb">`</span>; <span class="k">do</span> <span class="nv">$p</span>; <span class="k">done</span>
<span class="k"> </span><span class="nb">echo</span>
<span class="k">done</span>
</pre></div>
</td></tr></table><p>And the results -- perl and c are basically identical. Most of the time is
spent in the usleep anyway. Perl 5's super-low startup latency for short
scripts is amazing (ditto with lua, although lua takes a tiny hit here when it
loads its external socket library).</p>
<table border="1" class="docutils">
<colgroup>
<col width="23%" />
<col width="26%" />
<col width="26%" />
<col width="26%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head" colspan="4">Timing 100 iterations</th>
</tr>
</thead>
<tbody valign="top">
<tr><td> </td>
<td>c</td>
<td>perl</td>
<td>lua</td>
</tr>
<tr><td>real</td>
<td>0m2.304s</td>
<td>0m2.263s</td>
<td>0m2.596s</td>
</tr>
<tr><td>user</td>
<td>0m0.608s</td>
<td>0m0.512s</td>
<td>0m0.692s</td>
</tr>
<tr><td>sys</td>
<td>0m0.444s</td>
<td>0m0.332s</td>
<td>0m0.432s</td>
</tr>
</tbody>
</table>
greghttp://www.blogger.com/profile/10634561928684519035noreply@blogger.com0