Building Timelapse Aquarium Video

02/26/2014 Comments off

Aquarium Dropcam

My wife Kathleen and I keep a 29 gallon mixed tropical reef aquarium. For her birthday, I bought her a Dropcam webcam so we could both watch our reef when we’re away from home. Dropcam provides a live, hosted video stream and also has APIs for other data, like still snapshots.

Tropical reefs are teeming with life, though much of it moves at dramatically slower scales of time than we do. For example, when a coral captures food it can take 30 minutes to open it’s mouth, swallow the food, and digest a single bite. Snails, sea urchins, and other invertebrates slowly crawl the sand and glasswork in our tank.

Looking at a single picture of a tropical reef, it’s easy to miss how active the reef is. To show the activity, I wanted to create timelapse video of our aquarium.

Aqurium Timelapse for January 2014

This video distills the entire month of January from 2014 into a few minutes of video. At this scale, you can see our reef is teeming with life.

Dropcam Tools hacking

I started writing a toolkit that would capture a snapshot from our Dropcam every 10 minutes as a still image. Then, I wrote tools to stitch these still images together into timelapse video using ffmpeg.

You can find the source code on Github:

https://github.com/wpeterson/dropcam-tools

Categories: Aquarium, Open Source

Emoji Gem 1.0 Release

02/12/2014 Comments off

Emoji Gem:

Earlier this week, we are proud to release the complete 1.0 version of the emoji gem on RubyGem.org for public use.

What is Emoji?

Emoji are cartoonish icon characters, distributed as a UTF-8 font, like this heart: ❤. Using emoji began in Japan with mobile phones, but has grown in popularity throughout the internet over the last 3-4 years. Unfortunately, emoji character support is incomplete in many cases (like the Chrome browser). Users of Polar Polls easily add emoji on their iPhones and we wanted to support them. However, we needed a way to support emoji on unsupported web platforms.

Enter the emoji gem. The emoji gem bundles a comprehensive emoji index and a complete emoji image library from Phantom Open Emoji. This gem provides a fast and simple Ruby translation between UTF-8, moji characters, and image representations. Additionally, this gem implements a Rails engine that serves up the Emoji image library when replacing characters with images. This gem works in all Ruby interpreters, but will load a native-optimized string engine if it’s available (everywhere but JRuby).

Backstory

This project began after-hours at the Burlington Ruby Conference in August of 2013. I was having dinner with Steve Klabnik and a handful of other developers at Farmhouse, when Steve mentioned his plans for building an emoji gem. We had struggled with web support for emoji at Polar Polls after enabling emoji support in our iOS app. I exchanged information with Steve and hoped to work together on building a solution.

After the conference, I worked for several weeks putting the first working version together. Steve and I roughed out an outline for the architecture and tools. Shortly after, we opened up the gem to review/contribution from other folks. We were lucky to get several pull requests and I polished a version that Polar Polls could use in production. Over the last 6 months, we’ve collected a handful of fixes and performance tuning to prepare for our 1.0 release.

Thank You!

I’ve been a huge beneficiary of open source software and the community supporting Ruby/Rails. I’m glad to have a chance to build something useful and give back to the community. None of this would have been possible without the following folks:

  • @steveklabnik – for the idea, securing rights to “emoji” gem name, guidance, and publicity for the project
  • @ryan-orr – for transferring the emoji gem account for our new project
  • @mikowitz – for contributing code and adding Ruby String extensions.
  • @semanticart – for contributing code and expanding Ruby version support.
  • @parndt – for improving our docs and README.

How to Fix GMail Unread Messages Favicon

04/12/2013 Comments off

Living Without GMail’s Unread Message Icon feature

GMail provides an amazing Google Labs feature to show the unread message count in the Favicon of the GMail web page. If you use features like Chrome’s “Pin Tab” to manage your gmail tabs, this is essential to seeing when you have new mail. Unfortunately, this feature broke in public GMail sometime in October/November of 2012 based on public reports.

How to Fix Unread Favicon in GMail for Chrome

Thanks to Bertrand Schneider for the code snippet and fix.

Why did the favicon break?

It looks like there was a regression that causes two favicon link elements to be generated in the GMail markup, causing only the first to take precedence for the page (the default favicon) and the smart favicon with the unread count not to be shown.

Work-around for old GMail (on Google Apps domain)

Change the match string in the JavaScript only to match your public gmail account, for example https://mail.google.com/mail/u/0/* if your public GMail is your first logged in google account.

Categories: Uncategorized

ActiveModel Type Coercion and API Validation

02/03/2013 1 comment

ActiveModel Type Coercion

ActiveModel is a powerful ORM that looks up the data types for model values and applies automatic type coercion and casting for user data. Data coming from Web requests as JSON or query parameters are usually cast from Strings into other datatypes, implicitly, during assignment. This is a helpful feature 99% of the time, but can be frustrating when invalid values are silently discarded. Especially if you’re trying to use model level validations for API validation.

Optional API Value Validation

As we built the API for App Cloud, we put together rules for handling JSON input and validating data. For optional user input values, we decided that if a user provided invalid data, we should return a validation error vs. silently discarding invalid values. For example, if there was a “duration” value, that was an Integer number of days a value of “foo” should produce an error. However, the default behavior is to silently discard a value like “foo” assigned to a numeric field.

We know this has happened when the coerced value is nil but there is some user data present before the coercion. There’s an example Validator below implementing this logic.

A Coerced Value Validator

Digging into the source code, ActiveModel first assigns the value for a field ‘duration’ into an attribute ‘duration_before_type_case’, which preserves the raw value before coercing the input based on the type of the attribute. Using this, we can mix-in validations for models in our API that will mark the record invalid if data is silently discarded during type coercion. Before this, providing ‘foo’ as an integer value results in nil but no errors. Now, we’ll capture the error on this input.

class CoercedValueValidator < ActiveModel::Validator
  def validate(record)
    coercible_attrs = record.keys.select do |k,v|
      [Integer, Float, Date, Time].include?(v.type)
    end.map(&:first)

    coercible_attrs.each do |attr|
      if record.send("#{attr}_before_type_cast").present? && record.send(attr).nil?
        record.errors.add(attr, 'is invalid')
      end
    end
  end
end
Categories: Database, Rails

Cookies in Hybrid Android Apps

01/17/2013 9 comments

Sharing Cookies between WebViews and native code

Hybrid mobile applications require a close collaboration between native code and WebViews. Unlike iOS, Android doesn’t provide any implicit bridge between HTTP cookies in WebViews and native HTTP requests made through HttpConnection or Apache’s HttpClient interfaces. As a developer, it’s up to you to manually synchronize these systems if you want to share state or authentication context between them.

This is often a useful feature for integrating third party web services via their login page in your app’s WebView. Authentication credentials stored as HTTP cookies should be shared for subsequent HTTP calls, whether they’re native or a WebView.

Android Cookie Management and Persistence

Add CookieManager and CookieSyncManager hooks to properly setup your application to accept and save cookies, while writing them to disk periodically or when the application pauses or shuts down.

Application

public class YourApplication extends Application {
  public void onCreate() {
    super.onCreate();

    //Setup Cookie Manager and Persistence to disk
    CookieSyncManager.createInstance(this);
    CookieManager.getInstance().setAcceptCookie(true);
  }
}

Main Activity

public class MainActivity extends BaseActivity {
  public void onResume() {
    CookieSyncManager.getInstance().stopSync();
  }

   public void onPause() {
     CookieSyncManager.getInstance().sync();
   }
}

Synchronizing Android CookieManager to Apache HttpClient

Now that cookies have an application wide cookie store that will save WebView cookies and persist the cookie store to disk, we need to sync cookies from this store for use by native code.

Synchronize native HttpClient with App's Cookie Store
This example shows populating an Apache CookieStore configured in an HttpClient that is pre-populated with any HTTP Cookies that apply to this URL. Then, after executing the request, writing all of the cookies saves in the CookieStore into the App's Android CookieManager.

public static String getURLContentsAsString(String url) {

  DefaultHttpClient httpClient =  new DefaultHttpClient();

  //Injects App's CookieManager cookies for this URL into HttpClient CookieStore
  syncCookiesFromAppCookieManager(url, httpClient);

  HttpGet hp = new HttpGet(url);
  HttpResponse response = hc.execute(hp);

  //Save's cookies from HttpClient in App's CookieStore
  syncCookiesToAppCookieManager(url, httpClient);

  // Process Response here
  //...
}

syncCookiesFromAppCookieManager()
This helper function fetches all cookies for this URL from the Android CookieManager, which gives us a raw Cookie HTTP header value. The Apache CookieStore expects Cookie objects, which we will generate using the RFC2109Spec that matches the format of the Android CookieManager.

public static void syncCookiesFromAppCookieManager(String url, DefaultHttpClient httpClient) {

  BasicCookieStore cookieStore = new BasicCookieStore();
  httpClient.setCookieStore(cookieStore);

  CookieManager cookieManager = CookieManager.getInstance();
  if (cookieManager == null) return;

  RFC2109Spec cookieSpec = new RFC2109Spec();
  String rawCookieHeader = null;
  try {
    URL parsedURL = new URL(url);

    //Extract Set-Cookie header value from Android app CookieManager for this URL
    rawCookieHeader = cookieManager.getCookie(parsedURL.getHost());
    if (rawCookieHeader == null) return;

    //Convert Set-Cookie header value into Cookies w/in HttpClient CookieManager
    int port = parsedURL.getPort() == -1 ?
      parsedURL.getDefaultPort() : parsedURL.getPort();

    CookieOrigin cookieOrigin = new CookieOrigin( parsedURL.getHost(),
                                                  port,
                                                  "/",
                                                  false);
    List<Cookie> appCookies = cookieSpec.parse(
      new BasicHeader("set-cookie", rawCookieHeader),
      cookieOrigin);

    cookieStore.addCookies(appCookies.toArray(new Cookie[appCookies.size()]));
  } catch (MalformedURLException e) {
    // Handle Error
  } catch (MalformedCookieException e) {
    // Handle Error
  }
}

syncCookiesToAppCookieManager()
This helper extracts Cookie objects from the Apache CookieStore and converts them into Set-Cookie header strings for the Android CookieManager.

public static void syncCookiesToAppCookieManager(String url, DefaultHttpClient httpClient) {

  CookieStore clientCookieStore = httpClient.getCookieStore();
  List<Cookie> cookies  = clientCookieStore.getCookies();
  if (cookies.size() < 1) return;

  CookieSyncManager syncManager = CookieSyncManager.getInstance();
  CookieManager appCookieManager = CookieManager.getInstance();
  if (appCookieManager == null) return;

  //Extract any stored cookies for HttpClient CookieStore
  // Store this cookie header in Android app CookieManager
  for (Cookie cookie:cookies) {
    //HACK: Work around weird version-only cookies from cookie formatter.
    if (cookie.getName() == "$Version") break;

    String setCookieHeader = cookie.getName()+"="+cookie.getValue()+
      "; Domain="+cookie.getDomain();
    appCookieManager.setCookie(url, setCookieHeader);
  }

  //Sync CookieManager to disk if we added any cookies
  syncManager.sync();
}

Monitoring Redis Replication in Nagios

12/13/2012 Comments off

I’ve put together the following Nagios plugin for monitoring Redis slave server replication to ensure replication is successful and the lag is within a reasonable time limit:

#!/usr/bin/env ruby
require 'optparse'
options  = {}
required = [:warning, :critical, :host]

parser   = OptionParser.new do |opts|
  opts.banner = "Usage: check_redis_replication [options]"
  opts.on("-h", "--host redishost", "The hostname of the redis slave") do |h|
    options[:host] = h
  end
  opts.on("-w", "--warning percentage", "Warning threshold") do |w|
    options[:warning] = w
  end
  opts.on("-c", "--critical critical", "Critical threshold") do |c|
    options[:critical] = c
  end
end
parser.parse!
abort parser.to_s if !required.all? { |k| options.has_key?(k) }

master_last_io_seconds_ago = `redis-cli info | grep master_last_io_seconds_ago`.split(':').last.to_i rescue -1

status = :ok
if master_last_io_seconds_ago < 0 || master_last_io_seconds_ago >= options[:critical].to_i
  status = :critical
elsif master_last_io_seconds_ago >= options[:warning].to_i
  status = :warning
end

status_detail = master_last_io_seconds_ago == -1 ? 'ERROR' : "#{ master_last_io_seconds_ago.to_s }s"
puts "#{status.to_s.upcase} - replication lag: #{status_detail}"

if status == :critical
  exit(2)
elsif status == :warning
  exit(1)
end

This can be wired up in Nagios with an NRPE remote execution check:

define service {
    name                            redis_replication
    register                        1
    check_command                   check_nrpe!check_redis_replication!$HOSTNAME$ 100 250
    service_description             Redis Replication
    hostgroup_name                  redis_slave
}
Categories: Operations

MongoDB Indexing, count(), and unique validations

11/10/2012 Comments off

Slow Queries in MongoDB

I rebuilt the database tier powering App Cloud earlier this week and uncovered some performance problems caused by slow queries. As usual, two or three were caused by missing indexes and were easily fixed by adding index coverage. MongoDB has decent index functionality for most use cases.

Investigating Slow count() queries

Unfortunately, I noticed a large variety of slow queries issuing complex count() queries like:

{ 
  count: "users", 
  query: { 
    email: "bob@company.com", 
    _id: { $ne: ObjectId('509e83e132a5752f5f000001') }
  }, 
  fields: null 
}

Investigating our users collection, I saw a proper index on _id and email. Unfortunately, MongoDB can't use indexes properly for count() operations. That's a serious drawback, but not one I can change.

Where were these odd looking queries coming from? Why would we be looking for a user with a given email but NOT a given id?

The uniqueness validation on the email key of the User document and many other models was the culprit. Whenever a User is created/updated, ActiveModel is verifying there are no other Users with the given email:

class User
  include MongoMapper::Document

  key :email, String, unique: true
end

Use the Source!

Why is a unique validation triggering this type of count() query? Within Rails 3.x, this functionality is handled by the UniquenessValidator#validate_each implementation, which checks for records using the model's exists?() query:

  finder_class.unscoped.where(relation).exists?

The exists?() method is a convention in both ActiveRecord and MongoMapper, checking for any records within the given scope. MongoMapper delegates it's querying capability to the Plucky gem, where we can find the exists?() implementation using count():

  def exists?(query_options={})
    !count(query_options).zero?
  end

Root Cause and a Patch to work-around MongoMapper/Plucky

In SQL, using count() is a nice way to check for the existence of records. Unfortunately, since MongoDB won't use indices properly for count(), this incurs a big performance hit on large collections.

I added a MongoMapper patch to work-around the issue. We can patch the exists?() method to use find_one() without any fields instead of the expensive count() path:

module MongoMapper
  module Plugins
    module Querying
      module ClassMethods
        # Performance Hack: count() operations can't use indexes properly.
        # Use find() instead of count() for faster queries via indexes.
        def exists?(query_options={})
          !!only(:_id).find_one(query_options)
        end
      end
    end
  end 
end
Follow

Get every new post delivered to your Inbox.