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.