Sunday, August 14, 2011

Planet Eclipse Is Misrepresenting Me


Unfortunately the wrong feed URL for my Wordpress blog was fed into Planet Eclipse and so the following posts are not mine, even though they seem to have my name on them.
  • Robert Konigsberg: Senior Software Engineer
  • Robert Konigsberg: Cure for the itch
  • Robert Konigsberg: Java Developers (ALL LEVELS) San Antonio TX
  • Robert Konigsberg: 2 Fast thrice Furious
  • Robert Konigsberg: Eclipse CREDENTIAL_ERROR
The Eclipse bug around which all of this has been based has been updated with a request to correct the feed.

Thanks to Lars Vogel for pointing this out.

Saturday, June 25, 2011

WordPress: a three month experiment

I'm starting a three month experiment of using WordPress solely for blogging.

I still like Blogger's platform, but also the few times I've worked with WordPress I've really liked what it had to offer. So now's just as good a time as any to give it a three month trial.

I will not be updating this blog during this time. You can follow me at I've already put up my first post, which talks a little bit about using the things I discovered while getting started.

Thursday, May 26, 2011

Mac Word of the Day

I want to see if using Electric Sheep as my screen saver is the cause of recent OSX workstation lock-ups. So I scanned the available screen savers and settled on Word of the Day screen saver.

The first thing it published was:

polynomial: Mathematics of, relating to, or denoting a polynomial or polynomials. 

Yeah, that was useful. Thanks. I'll stick with dog photos.

Sunday, May 22, 2011

Tales of Pirx The Pilot

I learned about Stanislaw Lem in 1992 when a friend made a gift of The Cyberiad to me. He has become my favorite author ever since. I own all of his books translated to English, and one in Polish.

That isn't to say I've read all those books, save the untranslated one. No, there are several which remained unread or partially read over the last 20 years. Two of these are the series Tales of Pirx the Pilot, and More Tales of Pirx the Pilot. Though years back I read the first story in the series, The Test, a fantastic story that demonstrates a skill Lem excels at but rarely uses, that of slowly-building dramatic tension followed by a shocking and logical twist. Even  while re-reading The Test this past week for the second time in fifteen years, even knowing how the story ended, I couldn't help but forget everything but what was going on at that moment in the story, and still felt a genuine sense of shock by the revelation of the story's unspoken conceit. The only other time I experienced this sense of being drawn in, hooked on every single word, despite knowing how the story ends, is the story-within-a-story in Fiasco.

But now I've moved on to the second story in Tales, titled The Conditioned Reflex and it's delicious in the more standard way for him to write, which is his ability to describe fake science. Here Pirx is borrowing a space suit from the Russia's lunar station.
The Russian suit was unconventional in a number of ways. For one thing it had three, instead of two, visors: one for high Sun, one for low, and one - shaded dark orange - for dust. The air-valve arrangement was different, and it was rigged with inflatable boots that cushioned the impact of rocks and gripped even the slipperiest surface; they called it the "high-mountain model." Even the coloring was different: half in black and half in silver. When you stood with the black side facing the Sun, you broke out in a sweat; with the silver you were braced by a delicious coolness.

Pirx found the idea had one basic flaw in it: a man couldn't always be pointed in the direction of the Sun. So what was he supposed to do? Walk backward?

The others chuckled and called his attention to a color alternator located on his chest. If he adjusted the knob, the colors could be reversed: black in front, silver in back, and vice versa. The mechanics of it were interesting. The suit's outer ply was made of a clear, tear-resistant nylon fabric; between it and his body was a thin air barrier filled with two different kinds of dye, or rather semiliquid substances - one aluminized, the other carbonized...
Lem's view of space travel speaks to me more than the Roddenbery/Star Trek/space fantasy view. Space travel is inconvenient, difficult, a product of good and bad engineering, and can often fail because of small problems compounding on each other. From a favorite story of mine, The Seventh Voyage from The Star Diaries:
It was on a Monday, April second - I was cruising in the vicinity of Betelgeuse - when a meteor no larger than a lima bean pierced the hull, shattered the drive regulator and part of the rudder, as a result of which the rocket lost all maneuverability. I put on my spacesuit, went outside and tried to fix the mechanism, but found I couldn't possibly attach the spare rudder - which I'd had the foresight to bring along - without the help of another man. The constructors had foolishly designed the rocket in such a way, that it took one person to hold the head of the bolt in place with a wrench, and another to tighten the nut.
In the end, Lem's stories human failings and not technical ones, and typically that comes down to hubris - failure of the overconfident, the smart and foolish. A good dose of humility is your best asset in Lem's world, even when you're alone, in a damaged rocket, tearing out of control toward the Moon.


Thursday, April 28, 2011

PSE&G, Your Laziness Is A Liability

Dear PSE&G,

Thank you again for coming out in early November to repair the rotting gas line. To accomplish this you had to dig a deep hole, about three feet deep if I recall correctly. I was told the night of the repairs by your team on-site that someone would come fill that hole within that week. Nobody came. My wife has called you repeatedly. We've been given a variety of responses mostly between "We will dispatch somebody" to "It's not our problem" to "It's not our department". Whatever. It's April, and this is a safety issue. It's been six months. Fill the damn hole.

I'm pleased to show you pictures of your incomplete work.

Here's the strong piece of wood covering the hole. Look at those fun toys that kids can play with near by.

Look at that -- the whole pit is filled with water! Perfect for getting a quick jump on mosquito season.

Tuesday, April 26, 2011

Dygraphs Bugs and the Start of Automated Javascript Testing

Short version:

Someone contributed wrote a bunch of code to Dygraphs. It broke an existing behavior, but also inadvertently (or advertently)   fixed another one that was broken before his patch was submitted. The new broken behavior was more of a problem than the old broken behavior, so Nealie provided a fix which restored the old broken behavior. And then I went ahead and did a bunch of tests. And as always, patches are welcome!

Long version:

Nealie submitted a rather comprehensive patch to Dygraphs that changed the way zooming events behaved. Here's the use case that he inadvertently broke:
  • Open
  • Use the mouse to zoom in on a graph along both the X and Y axes.
  • Perform an unzoom by clicking the “Unzoom” button. This effectively calls g.updateOptions({dateRange: null, valueWindow: null})
  • Expected behavior: both the original x and y ranges are restored.
  • Actual behavior: Only the x-axis range is restored.
Now, here’s the use case that was broken before his patch, and subsequently fixed:
  • Open
  • Use the mouse to zoom in on a graph along both the X and Y axes.
  • Make the following call from a javascript console: g.updateOptions({})
  • Expected behavior: this should effectively be a no-op.
  • Actual behavior: The y-axis restores to the original range.
In other words, Nealie’s patch made g.updateOptions({}) work just fine, while breaking g.updateOptions({dateRange: null, valueWindow: null});

I figured this out by basically looking at a series of commits and checkout then out at different times along the main code path. You can see my work here. If you read that document, you’ll notice that when performing the same zoom operations via g.updateOptions({…}), neither bug appears. It’s only when a zoom is performed via mouse operations.

Much of this could have been avoided if we had automated tests. Fortunately, I’m working on that, and in fact an initial attempt at them is now in a pull request. It uses the js-test-driver framework. If we had these automated tests in place beforehand, then it might be safe to say that neither bug would have made it in to the main branch.

For instance, here’s a simple test that verifies that verifies calls to updateOptions should do what I expect:

RangeTestCase.prototype.testRangeSetOperations = function() {
  var graph = document.getElementById("graph");
  var g = new Dygraph(graph, ZERO_TO_FIFTY, { });
  assertEquals([10, 20], g.xAxisRange());
  assertEquals([0, 55], g.yAxisRange(0));

  g.updateOptions({ dateWindow : [ 12, 18 ] });
  assertEquals([12, 18], g.xAxisRange());
  assertEquals([0, 55], g.yAxisRange(0));

  g.updateOptions({ valueRange : [ 10, 40 ] });
  assertEquals([12, 18], g.xAxisRange());
  assertEquals([10, 40], g.yAxisRange(0));

  g.updateOptions({  });
  assertEquals([12, 18], g.xAxisRange());
  assertEquals([10, 40], g.yAxisRange(0));

  g.updateOptions({ dateWindow : null, valueRange : null });
  assertEquals([10, 20], g.xAxisRange());
  assertEquals([0, 55], g.yAxisRange(0));

To be fair, that test doesn’t really test the use cases above, where the mouse operations are the cause of broken behavior. I haven’t written those tests yet, but here’s one I did write, which verifies the behavior of a mouse double-click:

RangeTestCase.prototype.testDoubleClick = function() {
  var graph = document.getElementById("graph");
  var g = new Dygraph(graph, ZERO_TO_FIFTY, { });

  assertEquals([10, 20], g.xAxisRange());
  assertEquals([0, 55], g.yAxisRange(0));

  g.updateOptions({ dateWindow : [ 12, 18 ] });
  g.updateOptions({ valueRange : [ 10, 40 ] });
  assertEquals([12, 18], g.xAxisRange());
  assertEquals([10, 40], g.yAxisRange(0));

  var evt = document.createEvent('MouseEvents');
     'dblclick', true, true, document.defaultView,
     2, 0, 0, 0, 0
     0, false, false, false, false, 0, null);
  assertEquals([10, 20], g.xAxisRange());
  assertEquals([0, 55], g.yAxisRange(0));

You get the idea.

If you read those tests carefully, you would probably have four criticisms:
  1. Accessing to the private attribute canvas_ should be discouraged.
  2. All those magic numbers used for initializing the event.
  3. Having to actually dispatch the event via the DOM.
  4. Questioning browser compatibility - what browsers would that work on?
Here are my answers:
  1. Yes, you’re right. That should be fixed.
  2. Yes, you’re right. That should read like DygraphTestUtils.dispatchDoubleClick(g);
  3. You’re right about that one also. Right now the API doesn’t quite make that possible, but it could be worked on if necessary. (Psst … patches welcome!)
  4. Yeah, I have no idea if that would work on anything other than Chrome. One thing at a time.
Finally, just what will we do about this behavior? Here's what I think:

First, I think we should accept Nealie's bug fix. Second, figure out why and how the both cases can be fixed. Third, get some comprehensive automated tests in place, make the tests easy to write and the framework easy to run. Fourth, and probably most important, change the submission guidelines to require a) automated tests to be run as part of the patch submission process and b) require new tests to be part of patches of a certain level of complexity.

Saturday, April 16, 2011

Thoughts about Proxying the HTML5 canvas

This post is a blackboard for my thoughts around how to write tests that verify what's drawn on an HTML5 canvas. I'm still relatively new to Javascript, DOM and Canvas, so if there's an established solution to mocking canvas operations, let me know. My research hasn't yielded anything.

As part of my recent involvement with Dygraphs, I've become interested in automated Javascript testing. I'm currently writing some very basic automated sanity tests for Dygraphs using jstestdriver, and those tests should be committed to the Dygraphs repository fairly soon.

Many of the tests I want to write have to do with the graph's internal state, and not its visual aspects, but at some point I will need to test what's drawn. Because Dygraphs has an HTML5 canvas, I have limited options:
  1. Visual inspection. To some degree this is what we already do. Dygraphs has over 80 pages that represent visual tests. Most tests require a quick glance, but some require playing with values on the page to see how the rendering changes. (This last problem is something I've contributed to.) And finally we just can't look at every single test with every release. Besides, we're looking to create automated tests, so let's move on to the other choices.
  2. Perform pixel-perfect tests against golden images. This works well when setting up tests, but the lack of a consistent canvas implementation across all browsers means that I should expect to have to create a golden image per test per browser version. This type of test can also be a problem when making small changes (such as minor tweaks in color and so on) require generating new golden images across the board, which comes with its own problems.
  3. Perform fuzzy-match tests against golden images. This addresses many issues, but I still need to generate golden images. Not ideal.
  4. Pixel count. This isn't my idea; someone else suggested it. This could theoretically work, but it seems complicated. "Assert that the canvas has 50 blue pixels" reads less like an effect and more like a side-effect.
  5. Implement a fake canvas of some kind. This is the type of solution I want to implement. Replace the canvas with some object so I can verify that a line is drawn from a to b by writing something like assertSegment(point(0, 0), point(50, 50));.
What I really want is a proxy that not only receives calls from a client and logs them for post-operation validation, but also actually draws on a real canvas. That way in the event of a regression I can view the backing canvas, which will serve as a clue to what changed. It also helps me understand just what kind of test is being written in the first place.

Today I set about to prove out the idea of a canvas proxy, and the truth is, it's just not going to happen the way I want. Here are the two showstopping problems I see so far:
  1. You can't fake properties. (You really can't do that with Java and EasyMock either, but in Java you don't often see statements like = top;.
  2. The proxy has to be a DOM element. Otherwise calls like document.appendChild(canvas) will fail. HTML canvas elements, in the end, are just DOM elements. The Proxy is not. I could start by creating a DOM element, and add methods to it (such as getContext()) but while my experience with faking the DOM is limited, my intuition suggests that's a dead end.
The DOM element problem can be mitigated by faking not the canvas, but the rendering context that comes from calls to getContext('2d'). I always expected to need to fake out the context. This might work, but we will still have trouble with properties.
context.strokeStyle = "#000000";
context.fillStyle = "#FFFF00";
It may be possible to mitigate the properties problem with a hack that keeps properties in sync between the proxy and proxied object. For example:
ProxyContext = function(proxied) {
  this.__proxied = proxied;

ProxyContext.prototype.__copyIn = function() {
  this.strokeStyle = proxied.strokeStyle;
  this.fillStyle = proxied.fillStyle;

ProxyContext.prototype.__copyOut = function() {
  proxied.strokeStyle = this.strokeStyle;
  proxied.fillStyle = this.fillStyle;

ProxyContext.prototype.beginPath = function() {
This could work. I'm pretty sure it could work. Hey Internet, why won't this work?

I fear that a decent solution requires an abstraction layer that turns all mutations on the context into functions (e.g. context.strokeStyle becomes context.setStrokeStyle().) Is that good Javascript practice, or is that just not the Javascript way?

Never Underestimate the Value of Good Timing.

The weather system causing hurricaines in North Carolina is causing heavy thunderstorms here in New Jersey.

Because my back yard is poorly graded and doesn't drain water well, I gave it the name "Lake New Jersey." Typically the rain creates several small ponds. For the first time our yard is a real lake.

So I went to our basement to check for any signs of flooding. We have a french drain and two sump pumps. The pumps have been tested but never activated by the rain. While I didn't see any signs of flooding, I did see that one of our two sump pumps was unplugged. I plugged it in and for the first time I heard the whirr of a motor -- it was pumping! Good, and bad. I checked the other pump - it's not pumping -- yet.

Never underestimate the importance of good timing.

Wednesday, April 06, 2011

You Should Try Dygraphs

Over the last six months I've been working with an open source Javascript library called Dygraphs. Dygraphs, run by Dan Vanderkam, provides an interactive graphing library using the HTML5 canvas.

Four things have made me appreciate Dygraphs:
  • It's got an wide array of configurable options.
  • It has an active community on the mailing list.
  • The documentation continues to improve. The options listing above was a recent improvement that is now my regular go-to reference.
  • The tests. Really I should call them examples. Because I can just often analyze them and get 80% of the way to the solution of the problem I'm working on.
I've recently made a few contributions to the project, including 2-dimensional pan and zoom and displaying graphs in a log scale.

If you need a graphing library I recommend you give Dygraphs a try.

Monday, March 28, 2011

Unlimited NYT access as a subscriber

The upcoming New York Times paywall is causing a bit of an uproar all over the place. But I haven't much paid attention to it because of an email which said that "as a valued home delivery subscriber, you’ll get our All Digital Access package — which includes unlimited access* to and the NYTimes app for your smartphone and tablet — free." As for the asterisk, it says "*Mobile apps are not supported on all devices. Does not include e-reader editions, Premium Crosswords or The New York Times Crosswords apps. Other restrictions apply."

But that's enough for me.

We get the weekend papers only, and it looks as if in my area it's available for $3.15 per week.

If we didn't want the paper weekend edition it would be a different matter, but now you know why, while it's academically important to most of the world, I don't much care about this big todo at the moment.

Copy of the email follows.

Monday, March 21, 2011

Living with a smart, sweet dog

Tonight I came home late, at around 9:30PM and the first thing I always see when coming in the front door is her smiling face. Now, dogs don’t smile, but there’s definitely a very happy and excited look that Maggie has, and if you didn’t know her, or if you were afraid of dogs, I don’t think you would like that face very much because her front teeth and fangs are all out, and her eyes are narrow as if she were ready to attack.

I must pause writing at this moment because hers truly came up to get my attention by sitting patently at my desk and giving the quiet “growl” sound she learned gets our attention. I typically respond to these rumbly “mmmmmm”s by standing and saying “show me” which she uses as a sign to lead me through the house to the thing she wants: either the front door for a walk, or back door for yard play, or a toy for tug, or the “food station” for the things that come from there. But tonight to my surprise when I said “show me” she didn’t respond, so when I kneeled she showed me what she really wanted by rolling on her back and exposing her belly for some affection. While I pet her stomach she shows her standard reciprocal affection by putting her paw somewhere on my person (in this case my stomach) and pressing to make some firm contact.

Speaking of which, I’ll share a slightly personal story. Saturday morning I slept late, and Maggie didn’t bother me. So when she heard me trundle around upstairs, she came up to meet me. She was certainly in need of a walk, and she was coming up to ask me so. Now like I said, she will lead me in the direction where she wants to go. Typically she’ll stand facing the direction and then look back to see if I’m following. Now I should point out that since I just got out of bed, I wasn’t what you might call “dressed.” We met on the second floor landing, and rather than lead me downstairs, she led me back to my bedroom. In other words, she took me to get dressed before taking me downstairs for a walk. Neither of us taught her that. She figured that out on her own.

Getting back to tonight - so, after the greeting and the pets and the licks, I told her we were going to do some “training”. Sometimes “training” means working on commands and responses, but in this case we were just going to play with some of her brainier toys. These are a set of three toys made by Nina Ottonson.


When I bought the first of these toys I thought to myself “How the hell is Maggie going to learn how to use these?” But with some patient training from Beth, she was really able to master these. Here’s an old video of her doing the “Dog Smart” puzzle. I assure you she can do these puzzles much faster than she could back then in September.

We did all three of these puzzles tonight, and her performance was typical -- she solves Dogsmart with surprising speed, Dogfighter with reasonable confidence, and with Dogbrick needs a little work and a little help. Watching her struggle with Dogbrick I thought about learning in general, and how she struggled with moving the bricks: with her nose, her teeth and her paws. I have no doubt that with more regular practice she will master it. And this made me think about my own career, and how practice is what makes you good at a task. It doesn’t make you smarter; it just makes you more competent. It helps that Maggie is a smart dog to begin with, but the practice made her an expert at Dogsmart.

We finished the puzzles and with the few remaining pieces of her training treats we went over some basic commands: “on your side”, “stand”, and “stay”. I tried testing her on the very last treat by putting her “on her side” and making her “stay” while a training treat was about two inches from her face. She patiently waited for 30 seconds, her eyes moving between me and the treat. I asked her to “stand” and instead she moved for the treat. I gave her a surprised “Hey!” because .. you know .. hey! Yelling would be wrong, but admonishing her with a loud noise is fine.

Training must always end on a positive note, even if it’s for the stupidest thing, so I resolved to give her one more thing to do. I stood up and so did she so I told her to lie back “down”. After that I had her go “on your side”, and made her “stay”. But then I walked from the living room to the kitchen out of her sight and continued to repeat “stay”. I expected to hear the jingle of her collar tags when I opened the bag of treats, but nothing happened. I was stunned to return with her still “on your side” even though I had been out of sight for so long, and so she was rewarded with a large amount of treats.

Now as I conclude this post she’s lying on the carpet in my office. She fell asleep and is making the distinct barking noise she makes when she’s dreaming. Is she dreaming about a “squirrel”? That’s another word she knows.

Thursday, March 17, 2011

Rock, Paper, Scissors in today's New York Times.

The NY Times published an online article that lets you play Rock, Paper, Scissors against an AI opponent.

Fantastic. Thinking that the best way to play this thing was by being completely random about it, I immediately went to the nearest shelf of board games to find a six-sided die. Out of eleven board games, only one of them had a six sided die, and that box was still shrink-wrapped.

The computer can perform in a novice mode, where it learns as it plays, or in advanced mode, where it uses 200,000 previous games to inform its predictions on what I would do. Would the die result in a tie game?

Not really. In advanced mode it was looking to predict my moves based on prior moves, and my random element confounded it:

After 20 rounds: 9 W 6 T 5 L
After 30 rounds: 12 W 11 T 7 L
After 40 rounds: 16 W 14 T 10 L

Then I went ahead and tried this against the novice, but limited it to 20 rounds (I have to work at some point.) I was surprised to see that I had a very early lead, though with a majority of ties. By round 16 I was winning 6W 9T 1L, but then it won the last four rounds ending with 6W 9T 5L.

Not statistically significant, but I want to push more data through. That each round takes a couple of uninterruptable seconds didn't help much. The application also only shows the last few rounds, whereas I had hoped it would allow me to export game's full history.

But it certainly has me thinking about how I can inject some form of randomness into my gameplay.

Friday, March 11, 2011

Workspace Mechanic 0.0.6 released to the testing update site

I think enough progress has been made on the Workspace Mechanic for Eclipse to give an update. Most of this you already know if you subscribe to the mailing list.
  1. There is now an update site for stable releases and one for testing releases.
  2. The Workspace Mechanic for Eclipse no longer depends on org.mortbay for JSON parsing. It also no longer depends on Mylyn because it has its own popup.
  3. Tasks may be specified by URL. Actually, it's slightly more complicated than that, but you can read all about it in the design document, which will eventually be turned in to proper documentation.
  4. Prettier icons.
  5. I've published tests for the project, and added many more in the last month.
  6. Lots of issues closed - too many to mention.
  7. I saved the most exciting thing for last. Michael Pellaton is giving a talk at EclipseCon which mentions the Workspace Mechanic for Eclipse, as well as his project: Eclipse Team Etceteras. If you're at EclipseCon, check it out.
Please try it out by installing it from the testing repository, and be sure to provide feedback!

Update: 11:07PM I've updated it to 0.0.7, adding relative URI task paths and supporting LASTMOD preference tasks supplied by URI.

Monday, February 07, 2011

The Reddit Effect

Early this morning an old post of mine got voted up to the main page on Reddit. I may sift through the comments when my day settles down but I just want to show you the impact of the exposure, thanks to my Analytics page:

a 4147% growth. That's not 40x, that's 4000x, as evidenced by this next graph:

Looks like the Reddit comments wound up on my post's page. Do I really have to delete all the chaff?

Sunday, February 06, 2011

@RunAsJUnitTest, @RunAsPluginTest

After many idle months I'm starting to pick up work on the Workspace Mechanic for Eclipse, and that included finally publishing some of the tests that accompanied it.

And then, after starting to work on the tests some more, I realized I was missing something I used while writing plug-ins internally at Google, and so I copied them into the internal tests package. They're mostly documentation, but useful documentation. Here's the slightly reduced code for both of them. Their purpose should be clear:
 * Declares that this test can be run as a JUnit test, and does not require
 * the trappings of running as an Eclipse plug-in test.
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RunAsJUnitTest {
* Declares that this test must be run as a plug-in test.
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RunAsPluginTest {
Nobody likes running tests in the plug-in container if he or she doesn't have to. Surely, I'd much rather run nice fast JUnit tests, particularly when I've separated out code specifically for fast unit tests.

Of course, without infrastructure to support these, they're little more than documentation, but documentation that has type completion in the IDE.

So, what do you think? Would it be reasonable to have Eclipse support annotations like these? Do you think these have merit, or are utterly worthless?

Thursday, January 27, 2011

guava-osgi project gets a fresh update!

Thanks to Mikaël Barbero, the guava-osgi project has been revived!

At the new update site you can find bundles for Guava versions 3, 4, 5, 6 and 7. This is just in time for the imminent release of r08, and we will publish a plug-in shortly after its release.

Between me and Mikaël, guava-osgi will henceforth be updated just days after Guava releases. This is because Mikaël wrote a script that automates most of the nasty work.

Please give your thanks to Mikaël!

Thursday, January 13, 2011

fluid security

I flew out of SJC today. As mentioned on my Buzz post, I was worried about trying to bring a bottle of prescription cough syrup with codeine through an airport TSA checkpoint.

According to the TSA's website, each container must be 3.4 oz (100ml) or less. Medications are allowed in reasonable quantities exceeding 3 oz, and must be declared for inspection. I was also told to be prepared to verify that the name and address on the bottle correspond with my identification.

It's hard to read unless you view the full image but the bottle has tick marks for measuring contents. The bottle holds 120ml of liquid and at that time had only 100ml of liquid (which makes sense, I took two 10ml doses.) Of course, they wouldn't know that unless they examined the bottle.

Tick marks are visible in this photo
Gratuitous second photo

So when I passed through TSA I dutifully presented the bottle to an agent on the outside of the security checkpoint, who told me to pass it through with the rest of my luggage. He did not ask for ID to verify that I owned the contents. I walked through the metal detector and waited for my belongings.

However, there conveyer belt stopped moving followed by a long delay. The agent responsible for scanning the X-ray view for dangerous substances was definitely looking at something. I didn't know what he was looking at, nor did I care, I assumed I would be quizzed about the bottle after which I would present identification. Instead he called over another agent, and asked for her advice.

Eventually I found out what they were making a fuss about: the Nalgene bottle attached to the exterior of my backpack.
Don't they realize that only peace loving docile sheep travel with their own water bottles?
There was a small quantity of water left in the bottle and they could not figure our what to do. I offered to drink the water. They ignored my suggestion and continued conferring. After yet a little more time, they passed the bottle through, allowing me to board my flight home. They never inspected the bottle of cough syrup. So, a 120ml bottle with 100ml of liquid was unexamined, but a Nalgene bottle with a little water in it underwent great scrutiny.

Just how much water was in the Nalgene bottle? Let's look.

You should be able to make out the liquid at the bottom of the bottle. Below is another view of the scale on the bottle's side for reference.

Eyeball it .. two ounces? One ounce?

I dumped the contents into a shotglass:

Even criminals need water.

0.8 fluid ounces, which is 23.6 milliliters.

So they ignored a 120ml bottle (which ostensibly could have had 120ml of fluid) but stared at 23.6ml of water.

To be fair, it's possible they were discussing a finer point of procedure rather than the immediate potential risk of my water bottle, but it's not as if there wasn't a line of travelers behind waiting on this issue's resolution.

Chart Time!

Here are the quantities listed in this discussion. Remember, it's that small value they made they made the big fuss about.

Apologies to the color-blind

Thanks for reading. You may now proceed with the eye-rolling and advising better ways to spend time.

In the meanwhile, it's time to dose up on codeine and get some rest. Good night!

(ps no nudie scanners in use at SJC.)