2012-03-06

How fast does Vaadin 6 render a layout?

[Update: I altered this app to separate the time taken as overhead when launching the Vaadin app from the time taken to create a complex form. The app now launches with a simple form displaying a button that takes the user to a complicated form.]

"Peter" on StackOverflow.com claimed that a complicated form takes 10 seconds with Vaadin, but less than a second on some other web app framework. I took that as a challenge. Using his specification:
form with 3 column - each column contains 6*label+input, 2 tables, 12 buttons
I created a Vaadin 6.7.5 app that looks like this (when shrunken down):

Hopefully you may find the app up and running.

My Results

My results when launching this app and rendering this layout…

  • Less than a single second when run locally on my MacBook, Intel Core 2 Duo at 2.4 GHz.
  • 8-12 seconds the very first time served from Apache Tomcat 7 on a Mac mini 2.53 GHz running Mac OS X Snow Leopard across the Internets via DSL connections or a Starbucks’ wifi.
  • After the app launches, a complex form can be built and displayed in a second or two. 
So, in the usual case of deployment using modest resources, a Vaadin app’s initial launch has an overhead of about 8-12 seconds. But after initialization, layouts appear in a second or so.

Furthermore, I added some tests for elapsed time within the app. That testing shows that server-side creation of the complex form takes only about 7 milliseconds.  Each GridLayout instance takes about 1-2 milliseconds. So most of that second or two it takes for the form to appear must be time spent transmitting the description of the form to the browser and then the browser rendering on screen.

About My Implementation

I tried not to "cheat" by over-optimizing. I generate three instances of the same GridLayout, each representing the "3 columns" mentioned by Peter. The buttons each have a separate listener object that react to a click. Each of the tables is created with its own set of data -- no sharing. Seems like a realistic setup to me.

Here is the source code. Contained in a single class for your copy-paste convenience. I’m sorry Blogger.com has no support for presenting source code.


package com.example.triplegrid;


import com.vaadin.Application;
import com.vaadin.ui.*;
import com.vaadin.ui.Button.ClickEvent;


/**
 * This example Vaadin 6.7.5 app was created to meet a challenge by "Peter" on StackOverflow.com
 * http://stackoverflow.com/questions/3888233/cons-related-to-vaadin
 * 
 * Peter claims that a complicated layout such as this takes 10 seconds to load.
 * 
 * I found that launching this app and rendering this layout takes less than a second
 * when run locally on my MacBook, Intel Core 2 Duo at 2.4 GHz.
 * 
 * I found that launching this app with the simple layout over the Internet to a Starbucks' wifi
 * takes 12 seconds the very first time. Apparently the initial loading of the Vaadin framework’s
 * libraries to the web browser takes about 10-12 seconds to download and initialize. Clicking the
 * "Create triple grid layout" button takes less than 2 seconds to create and show the layout.
 * So, yes, a Vaadin app has 10 second startup overhead. But after that complex forms need only a 
 * second or two to appear.
 * Served from Apache Tomcat 7 on a Mac mini 2.53 GHz running Mac OS X Snow Leopard.
 * 
 * Peter gave this brief description as the specification for his form:
 * form with 3 column - each column contains 6*label+input, 2 tables, 12 buttons
 * I created 3 GridLayout layouts in Vaadin to implement Peter's 3 columns.
 * 
 * © 2012 Basil Bourque. This source code may be used freely forever by anyone taking full responsibility for doing so.
 * 
 * @author Basil Bourque
 * 
 */
public class TriplegridApplication extends Application {


    HorizontalLayout outerLayout = null;


    @Override
    public void init() {
        // Challenge by "Peter" of StackOverflow:
        // form with 3 column - each column contains 6*label+input, 2 tables, 12 buttons


        // To meet that challenge, I am using 3 instances of a GridLayout, one for each of Peter's 3 columns.


        long appStart = System.currentTimeMillis();


        this.outerLayout = new HorizontalLayout();
        this.outerLayout.setMargin( true );
        this.outerLayout.setSpacing( true );


        Label appInitWhen = new Label( "App’s init() method ran: " + new java.util.Date().toString() );
        this.outerLayout.addComponent( appInitWhen );
        
        Button createTripleGridButton = new Button( "Create triple grid layout" );
        createTripleGridButton.addListener( new Button.ClickListener() {


            @Override
            public void buttonClick( ClickEvent event ) {
                long tripleGrid_Start = System.currentTimeMillis();
                createAndShowTripleGrid();
                long tripleGrid_Elapsed = System.currentTimeMillis() - tripleGrid_Start;
                getMainWindow().setCaption( "Triple Grid - New form: " + tripleGrid_Elapsed + " ms" );
            }


        } );
        this.outerLayout.addComponent( createTripleGridButton );


        Window window = new Window( "Triplegrid Application" );
        window.setContent( outerLayout );


        long appElapsed = System.currentTimeMillis() - appStart;
         window.setCaption( "Triple Grid - App initialization: " + appElapsed + " ms");
        setMainWindow( window );
    }


    public void createAndShowTripleGrid() {


        Button closeAppButton = new Button( "Restart app" );
        closeAppButton.addListener( new Button.ClickListener() {


            @Override
            public void buttonClick( ClickEvent event ) {
                close(); // Close this app instance.


            }


        } );


        Layout grid1 = this.makeGridLayout();
        Layout grid2 = this.makeGridLayout();
        Layout grid3 = this.makeGridLayout();


        this.outerLayout.removeAllComponents();
        outerLayout.addComponent( closeAppButton );
        this.outerLayout.addComponent( grid1 );
        this.outerLayout.addComponent( grid2 );
        this.outerLayout.addComponent( grid3 );
    }


    private GridLayout makeGridLayout() {
        // Challenge: each column contains 6*label+input, 2 tables, 12 buttons)
        // We'll do 6 rows of label, field, button, button.
        // Then we'll add 2 tables at bottom.


        long gridStart = System.currentTimeMillis();


        GridLayout grid = new GridLayout();
        grid.setSpacing( true );
        grid.setMargin( true );
        grid.setColumns( 4 );
        grid.setRows( 6 + 2 ); // 6 rows of fields, plus 2 rows of a Table each.


        for ( int i = 1; i <= 6; i++ ) {
            Label label = new Label( "label " + i );
            label.setSizeUndefined(); // Fix a quirky "feature" of Vaadin that Labels by default can collapse (not display) in GridLayout).
            grid.addComponent( label );
            TextField field = new TextField();
            field.setWidth( ( "Stop ms: 1331142285223".length() - 9 ) + "em" ); // Use example text to gauge width.
            grid.addComponent( field );
            grid.addComponent( this.makeButton( "alpha" ) );
            grid.addComponent( this.makeButton( "beta" ) );
            // GridLayout's cursor auto-wraps to next row of GridLayout because we used the last (4th) column.
        }


        // Add 2 tables.
        Table table1 = this.makeTable( "First of two tables" );
        Table table2 = this.makeTable( "Second of two tables" );
        // Each table spans across all four columns of a single row.
        // GridLayout's cursor auto-wraps to next row of GridLayout because we used the last (4th) column.
        grid.addComponent( table1, 0, grid.getCursorY(), grid.getColumns() - 1, grid.getCursorY() );
        grid.addComponent( table2, 0, grid.getCursorY(), grid.getColumns() - 1, grid.getCursorY() );


        TextField startField = (TextField) grid.getComponent( 1, 0 );
        TextField stopField = (TextField) grid.getComponent( 1, 1 );
        TextField elapsedField = (TextField) grid.getComponent( 1, 2 );


        long gridStop = System.currentTimeMillis();
        long gridElapsedTimeMillis = ( gridStop - gridStart );
        startField.setValue( "Start ms: " + gridStart );
        stopField.setValue( "Stop ms: " + gridStop );
        elapsedField.setValue( "Elapsed ms: " + gridElapsedTimeMillis );


        return grid;
    } // End of method.


    private Button makeButton( String caption ) {
        Button button = new Button( caption );
        button.addListener( new Button.ClickListener() {


            @Override
            public void buttonClick( ClickEvent event ) {
                getMainWindow().showNotification( "Ignore me", "<br/>This button does nothing." );
            }
        } );
        return button;
    } // End of method.


    private Table makeTable( String caption ) {
        Table table = new Table( caption );
        table.setSelectable( true );


        /*
         * Define the names and data types of columns.
         * The "default value" parameter is meaningless here.
         */
        table.addContainerProperty( "First Name", String.class, null );
        table.addContainerProperty( "Last Name", String.class, null );
        table.addContainerProperty( "Year", Integer.class, null );


        /* Add a few items in the table. */
        table.addItem( new Object[] { "Nicolaus", "Copernicus", new Integer( 1473 ) }, new Integer( 1 ) );
        table.addItem( new Object[] { "Tycho", "Brahe", new Integer( 1546 ) }, new Integer( 2 ) );
        table.addItem( new Object[] { "Giordano", "Bruno", new Integer( 1548 ) }, new Integer( 3 ) );
        table.addItem( new Object[] { "Galileo", "Galilei", new Integer( 1564 ) }, new Integer( 4 ) );
        table.addItem( new Object[] { "Johannes", "Kepler", new Integer( 1571 ) }, new Integer( 5 ) );
        table.addItem( new Object[] { "Isaac", "Newton", new Integer( 1643 ) }, new Integer( 6 ) );


        table.setPageLength( table.size() ); // Draw table just big enough to show all rows.
        return table;
    } // End of method.


}  // End of class.

2012-02-13

Help with Argument Validation in Java

The blog post Google Guava vs Apache Commons for Argument Validation is exactly that, a concise well-written comparison of the two. Conclusion: They are both good, but Guava may be a bit cleaner/simpler than Commons.

This post is one amongst many in this good list of Guava-related resources.

2012-02-09

Ditto on My Opinion of Vaadin

This article echoes my opinion about Vaadin toolkit for building business/desktop-style apps that happen to be delivered in a web browser.

2012-01-29

Disabling Annoying Window-Opening Animation in Lion

I'm liking my new Lion install. Mac OS X 10.7.2 seems to run faster than ever, especially in terms of graphics. And I like Apple's choice to generally tone-down the colors and flashiness in the user interface. Apple heads in the opposite direction of Microsoft as Redmond continues to turn up the visual volume-knob on the in-your-face interface of Windows.

Unfortunately, Apple made one poor choice. Lion now has a subtle but annoying animation on every single window as it opens. A zooming rectangle grows from a central point outwards before the actual window appears. It serves no useful purpose. May sound like no big deal, but after working a long while and staring at the screen, all those window-zooms made me dizzy.

While System Preferences is ignorant of this feature, nevertheless you can disable this animation. Just copy-paste this single line in Terminal.app:

defaults write NSGlobalDomain NSAutomaticWindowAnimationsEnabled -bool NO

I found this useful tip in the amazingly thorough review of Lion by John Siracusa. I highly recommend that article when you upgrade to Lion.

Another animation to kill: the shrinking/zooming effect when triggering Mission Control. To disable:
defaults write com.apple.dock expose-animation-duration -int 0; killall Dock
To re-enable:
defaults delete com.apple.dock expose-animation-duration; killall Dock
If only we could kill the nauseating sliding of entire screen when using the shortcut (two-finger swipe on Magic Mouse) to switch between Spaces or full-screen apps.

Port Scanning

If you need to see if your Mac has any software currently listening on a particular port, you can run this at the command-line (in Terminal.app or Path Finder):

/Applications/Utilities/Network\ Utility.app/Contents/Resources/stroke 127.0.0.1 5432 5432

That example is looking for the default port to the Postgres database server. Another example would be Apache Tomcat and other web servers that default to port 8080:

/Applications/Utilities/Network\ Utility.app/Contents/Resources/stroke 127.0.0.1 8080 8080

Why repeat the port number? Because you can scan a range of ports. The first number is the start of a range, the second number is the end of the range.

And besides this command-line approach, you can use the Network Utility app bundled with your Mac in the Utilities folder:
/Applications/Utilities/Network Utility.app

2012-01-25

Enabling JRebel

To use the current version of JRebel in Eclipse Indigo, you must make these settings in each project:

  1. Turn on Build Automatically from Project menu.
  2. Generate rebel.xml file by context-clicking the project name. 
For web work:
  1. When defining a server such as Tomcat or Jetty, set the Publishing property to Never publish automatically.
The JRebel configuration wizard says: For Web projects, JRebel expects to find the rebel.xml configuration file in the WEB-INF/classes directory in the target .war file.

2012-01-16

New Web Site for 4D Plugins by Heubach Media

Heuback Media has launched a handsome new web site dedicated to their excellent 4D plugins. Spun off from their main web site Heubach-Media.de, the new hmPlugins.com web site focuses on their commercial plugins hmCal, hmReports, hmTAPI (phone integration), hmBarCode, and the handy free-of-cost hmFree.

Note that old links to the old home of these plugins are now broken.