<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Shahzad Bhatti</title>
	<atom:link href="http://weblog.plexobject.com/?feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://weblog.plexobject.com</link>
	<description>Welcome to my ramblings and rants!</description>
	<lastBuildDate>Wed, 06 Mar 2013 21:25:25 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Tour of iOS game &#8211; &#8216;Passion Investment Portfolio&#8217;</title>
		<link>http://weblog.plexobject.com/?p=1699</link>
		<comments>http://weblog.plexobject.com/?p=1699#comments</comments>
		<pubDate>Wed, 06 Mar 2013 21:25:25 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[iPhone development]]></category>

		<guid isPermaLink="false">http://weblog.plexobject.com/?p=1699</guid>
		<description><![CDATA[I recently finished an iOS (iPhone/iPad/iPod Touch) game called "Passion Investment Portfolio", which provides a virtual stock trading for learning purpose. A unique feature of this application is that it chooses stocks for you based on your lifestyle. Wh]]></description>
			<content:encoded><![CDATA[<p>I recently finished an iOS (iPhone/iPad/iPod Touch) game called <a href="https://itunes.apple.com/us/app/passion-portfolio/id590374632?mt=8">&#8220;Passion Investment Portfolio&#8221;</a>, which provides a virtual stock trading for learning purpose. A unique feature of this application is that it chooses stocks for you based on your lifestyle. When you join this game, you receive $10,000 virtual cash money and then you are asked to add your life passions, interests, hobbies, products you like, stores where you shop, etc. The <a href="https://itunes.apple.com/us/app/passion-portfolio/id590374632?mt=8">Passion Investment Portfolio</a> game then finds companies that match your passions. It keeps updating those companies on daily basis and gives away free stocks from your matching companies. You can watch which companies grow with time and customize your favorite companies. The <a href="https://itunes.apple.com/us/app/passion-portfolio/id590374632?mt=8">Passion Investment Portfolio</a> game also provides analysts ratings, charts, fundamental data and news for your companies. Here is a short tour of the app:</p>
<h3>Adding your passions</h3>
<p> On iPhone/iPod touch, you can just select &#8216;My Passions&#8217; from the menu, then choose category of passion. You can add new passions by touching (+) icon. You can also edit your existing passions by touching them and rewriting your passions. If you need to delete any passion, then select Edit option and touch (-) button.<br />
 <br />
 <img src="/images/passion_iphone.png"><br />
 <br />
 On iPad, you can see categories and list of passions on the same screen but the process of adding or removing passions is same as iPhone, e.g.<br />
 <br />
 <img src="/images/passion_ipad.png" width="600" wheight="500"><br />
 </p>
<h3>Your portfolio</h3>
<p> When you initially join &#8220;Passion Investment Portfolio&#8221;, you don&#8217;t have any existing stocks, but as you play the game, you will see how your portoflio grows over time, e.g.<br />
 <br />
 <img src="/images/portfolio_iphone.png"><br />
 <br />
 <img src="/images/portfolio_ipad.png" width="600" height="500"><br />
 </p>
<h3>Your Passion Companies</h3>
<p> You can view your passion companies by selecting &#8220;My Passion Quotes&#8221; from the menu. You will be able to see all companies next to each passion along with delayed quotes. Also, you will be able to see thumbs up sign if the analysts rating is buy and thumbs down sign if analysts rating is sell.<br />
 <br />
 <img src="/images/passion_quote_iphone.png"><br />
 <br />
 <img src="/images/passion_quotes_ipad.png" width="600" wheight="500"> </p>
<h3>Your Stocks</h3>
<p> You can view all the stocks you own by selecting &#8220;My Stocks&#8221; from the menu.  You will also see latest quotes for those companies and gain/loss for your stocks based on market prices. If you choose to dump the stocks, then just touch &#8220;Sell&#8221; button.<br />
 <br />
 <img src="/images/stocks_iphone.png"><br />
 <br />
 <img src="/images/stocks_ipad.png" width="600" wheight="500"> </p>
<h3>Your Capital Gains/Loss</h3>
<p> When you sell stocks, the difference between initial purchase and final sell price is calculated and is recorded as your capital gain/loss. You can view history of all your capital gains by selecting &#8220;My Capital Gains&#8221;:<br />
 <br />
 <img src="/images/gains_iphone.png"> </p>
<h3>Orders History</h3>
<p> When you buy or sell stocks or the game buys stocks for free giveaways, an order is created. You can browse past orders by selecting &#8220;My Orders&#8221;.<br />
 <br />
 <img src="/images/orders_iphone.png"><br />
 <br />
 <img src="/images/orders_ipad.png" width="600" wheight="500"> </p>
<h3>Quote Lookup</h3>
<p> You can lookup any stock by symbol, name or description using &#8220;Quote Lookup&#8221; option. You can view delayed quotes, fundamental data, analysts rating and news.<br />
 <br />
 <img src="/images/quote_iphone.png"><br />
 <br />
 <img src="/images/quote_ipad.png" width="600" wheight="500"> </p>
<h3>Badges and Leaderboard</h3>
<p> The <a href="https://itunes.apple.com/us/app/passion-portfolio/id590374632?mt=8">&#8220;Passion Investment Portfolio&#8221;</a> game gives away badges based on performance of your portfolio and top users are listed on the leaderboard.</p>
<h3>Help and Feedback</h3>
<p> The <a href="https://itunes.apple.com/us/app/passion-portfolio/id590374632?mt=8">&#8220;Passion Investment Portfolio&#8221;</a> also comes with help tips and a quick way to send any feedback, suggestions and comments.<br />
 <br />
 <img src="/images/help_ipad.png" width="600" wheight="500"> </p>
<h3>Screencasts</h3>
<p> You can also watch at <a href="http://www.youtube.com/watch?v=NovYiq_v8pw">for iPhone</a> and <a href="http://www.youtube.com/watch?v=a4DNDrs0tQA">for iPad</a> respectively.</p>
<h3>Download</h3>
<p> You can download the app from <a href="https://itunes.apple.com/us/app/passion-portfolio/id590374632?mt=8">Apple Appstore</a>. Here are a few promotion codes that you can use to try the game (first come first serve):</p>
<p> RRTWFNNJRTAY<br />
 3WM96W9NLYTK<br />
 KLR7PE963K7E<br />
 NWXWJ7ATY6R7<br />
 7NHREJHWJMTH<br />
 XJPFHLAAXJLW<br />
 PTJXP6XXFRP3<br />
 FHLNATLKNRR3<br />
 X3KHXXT9LAY6<br />
 KKMJWA76MW7W<br />
 FHYR6XFLXJR3<br />
 3F6HHETJWR36<br />
 K6XJLMARMMET<br />
 H63RRP44KWWH<br />
 3P7KH763TEMA<br />
 X7NA4R7YN3H7<br />
 MX43TPNEJLL</p>
<h3>Twitter</h3>
<p> Follow the app at <a href="http://twitter.com/#!/passionportfol">http://twitter.com/#!/passionportfol</a>.</p>
<p> <script type="text/javascript">
      SyntaxHighlighter.all()
 </script> </p>
<div style='background-color: white'>
<div id="fb-root">
 </div>
<p> <script>
      window.fbAsyncInit = function() {
           FB.init({appId: '119853371386770', status: true, cookie: true, xfbml: true});
         };
         (function() {
           var e = document.createElement('script'); e.async = true;
           e.src = document.location.protocol +
             '//connect.facebook.net/en_US/all.js';
           document.getElementById('fb-root').appendChild(e);
         }());
 </script></p>
<div id="facebook">
   <fb:comments xid="sebh" simple="1" publish_feed="false" width="642" title='a comment on "Passion Investment Portfolio"'><br />
   </fb:comments>
 </div>
</p></div>
]]></content:encoded>
			<wfw:commentRss>http://weblog.plexobject.com/?feed=rss2&amp;p=1699</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Comparing Server side Websockets using Atmosphere, Netty, Node.js and Vertx.io</title>
		<link>http://weblog.plexobject.com/?p=1698</link>
		<comments>http://weblog.plexobject.com/?p=1698#comments</comments>
		<pubDate>Mon, 08 Oct 2012 13:31:22 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Javascript]]></category>

		<guid isPermaLink="false">http://weblog.plexobject.com/?p=1698</guid>
		<description><![CDATA[My last two bogs (<a href="http://weblog.plexobject.com/?p=1695">Pub/sub in Node.js and Websockets</a> and <a href="http://weblog.plexobject.com/?p=1697">Scaling Node.js</a>) described how I have been evaluating Node.js and Websockets for streaming data. ]]></description>
			<content:encoded><![CDATA[<p>My last two bogs (<a href="http://weblog.plexobject.com/?p=1695">Pub/sub in Node.js and Websockets</a> and <a href="http://weblog.plexobject.com/?p=1697">Scaling Node.js</a>) described how I have been evaluating Node.js and Websockets for streaming data. I would describe results of that evaluation along with a few other frameworks that I have been testing.</p>
<h3>Use Case</h3>
<p> The primary use case for my application is pub/sub where clients subscribe to some channel and receive updates such as subscribing for stock quotes or notifications about orders.</p>
<h3>Node.js</h3>
<p> In my last blog, I described all the issues I had with <a href="http://socket.io/">socket.io</a> library for Node.js and after a lot of frustration I gave up on it. Instead, I used <a href="http://einaros.github.com/ws/">WS</a> library for WebSockets and though it doesn&#8217;t provide fallback options for other protocols but&#8217;s it much more stable and efficient. </p>
<h4>Pub/Sub Server</h4>
<p> Here is the pub/sub server that provides methods to subscribe some channel identifier and sends update to a random channel continuously.</p>
<pre class="brush: javascript">
 var OUTLIER_SIZE = process.env.OUTLIER_SIZE || 5;
 var MAX_THREADS = process.env.MAX_THREADS || 5;
 var MAX_ROWS = process.env.MAX_ROWS || 10;
 var SEND_MESSAGE_INTERVAL = SEND_MESSAGE_INTERVAL || 1;

 var nextClientId = 0;
 var errors = 0;
 //
 var hwUsage = require('hardware_usage');
 var metrics = require('metrics')(OUTLIER_SIZE);
 var cluster = require('cluster');
 hwUsage.start();

 if (cluster.isMaster) {
     for (var i = 0; i &lt; MAX_THREADS; i++) {
         cluster.fork();
     }
     cluster.on('exit', function(worker, code, signal) {
     });
     console.log('clients, errors, ' + metrics.heading() + ',' + hwUsage.heading());
 } else {
     var pubsubController = require('pubsub_controller')(cluster.worker.id, MAX_ROWS, hwUsage, metrics);
     var WebSocketServer = require('ws').Server
     , wss = new WebSocketServer({port: 8124});
     wss.on('connection', function(ws) {
         ws.key = ++nextClientId;
         ws.on('close', function() {
             pubsubController.unsubscribeAll(ws);
         });
         ws.on('error', function() {
             errors++;
         });
         ws.on('message', function(text) {
             var msg = JSON.parse(text);
             if (msg.action === 'subscribe') {
                 pubsubController.subscribe(ws, msg.identifier);
             } else if (msg.action === 'unsubscribe') {
                 pubsubController.subscribe(ws, msg.identifier);
             }
         });
     });

     setInterval(function() {
         pubsubController.push();
     }, SEND_MESSAGE_INTERVAL);
     setInterval(function() {
         hwUsage.stop(function(usage) {
             console.log(nextClientId+ ',' + errors + metrics.summary().toString() + ',' + usage.toString());
         });
     }, 15000);
 }

 process.on('uncaughtException', function(err) {
     console.log("GLOBAL " + err + "\n" + err.stack);
 });
</pre>
<h4>Subscription management</h4>
<p> Following module defines APIs for managing subscriptions and as I mentioned when number of messages received by client reaches maxMessages, it unsubscribes from the server.</p>
<pre class="brush: javascript">
 var helper = require('helper');

 module.exports = function(workerId, numElements, hwUsage, metrics) {
     var channelSubscribers = {};
     var nextRequestId = 0;
     var allKeys = {};
     allKeys[workerId] = [];
     var generatePayload = function(identifier) {
         var elements = [];
         var date = new Date();
         for (var i=0; i&lt;numElements; i++) {            elements.push({sequence:i, price: helper.random(100), identifierId:identifier, symbol:"QQQ", transaction:"STO", qty: helper.random(200)});
         }
         var reqId = Number(++nextRequestId + '.' + workerId);

         var payload = {request: reqId, rows:numElements, timestamp : date.getTime(), identifier:Number(identifier), elements: elements};
         return payload;
     };
     var makeKey = function(socket) {
         return workerId + '_' + socket.key;
     };
     var getSubscriber = function(socket) {
         return channelSubscribers[makeKey(socket)];
     };
     var setSubscriber = function(socket, rec) {
         channelSubscribers[makeKey(socket)] = rec;
     };
     var removeSubscribers = function(socket) {
         delete channelSubscribers[makeKey(socket)];
     };
     return {
         subscribe: function(socket, identifier) {
             var rec = getSubscriber(socket);
             if (typeof rec === "undefined") {
                 rec = {socket : socket, identifiers : []};
             }
             rec.identifiers.push(identifier);
             allKeys[workerId].push(makeKey(socket));
             setSubscriber(socket, rec);
         },
         unsubscribe: function(socket, identifier) {
             var rec = getSubscriber(socket);
             if (typeof rec === "undefined") {
                 return false;
             }
             var ndx = rec.identifiers.indexOf(identifier);
             rec.identifiers.splice(ndx, 1);
             if (rec.identifiers.length == 0) {
                 removeSubscribers(socket);
             } else {
                 setSubscriber(socket, rec);
             }
         },
         unsubscribeAll: function(socket) {
             removeSubscribers(socket);
         },
         count: function() {
             return helper.size(channelSubscribers);
         },
         push: function() {
             if (allKeys[workerId].length == 0) return 0;
             var key = allKeys[workerId][helper.random(allKeys[workerId].length)];
             var rec = channelSubscribers[key];
             if (typeof rec === "undefined") {
                 return -1;
             }
             //
             for (var i=0; i&lt;rec.identifiers.length; i++) {
                 var identifier = rec.identifiers[i];
                 try {
                     var request = generatePayload(identifier);
                     var text = JSON.stringify(request);
                     rec.socket.send(text);
                     metrics.update(nextRequestId, new Date().getTime(), text.length);
                 } catch (err) {
                     console.log('failed to send execution report' + err);
                     try {
                         rec.socket.disconnect();
                     } catch (ex) {
                         console.log('failed to close socket ' + ex);
                     }
                     delete channelSubscribers[key];
                 }
             }
         }
     };
 }
</pre>
<p> I am skipping some of helper modules, but you can find them from the <a href="/nodejs_cmp.zip">source code</a>.</p>
<h3>Vertx.io</h3>
<p> The <a href="http://vertx.io/">Vertx.io</a> framework follows Node.js design for single-thread event loop with asynchronous I/O but is written in Java. It also provides polyglot support for a number of languages such as Java, Javascript, Ruby, Groovy, and Python. Here is similar implementation of Pub/Sub server in Java:</p>
<pre class="brush: java">
 package com.plexobject.vertx;

 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.core.JsonFactory;
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.databind.JsonNode;

 import org.vertx.java.core.Handler;
 import org.vertx.java.core.buffer.Buffer;
 import org.vertx.java.core.http.HttpServerRequest;
 import org.vertx.java.core.http.ServerWebSocket;
 import org.vertx.java.deploy.Verticle;

 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

 import java.io.IOException;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.Random;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.atomic.AtomicLong;

 public class VertxServer extends Verticle {
     private static final Logger logger = LoggerFactory.getLogger(VertxServer.class);
     private final ObjectMapper mapper = new ObjectMapper();
     private final JsonFactory jsonFactory = mapper.getJsonFactory();
     private Map&lt;String, Set&lt;ServerWebSocket>> subscriptions = new ConcurrentHashMap&lt;String, Set&lt;ServerWebSocket>>();
     private Timer sendTimer = new Timer(true);
     private Timer metricsTimer = new Timer(true);
     private final AtomicLong nextRequestId = new AtomicLong();
     private final Metrics metrics = new Metrics();
     private final StringBuilder elements = new StringBuilder();
     private final Random random = new Random();
     private int maxIdentifier;

     public VertxServer() {
         logger.info("identifiers," + Metrics.getHeading());
         for (int i=0; i&lt;500; i++) {
             elements.append(String.valueOf(i));
         }
         metricsTimer.schedule(new TimerTask() {
             @Override
             public void run() {
                 logger.info(subscriptions.size() + metrics.getSummary().toString());
             }
         }, 5000, 15000);
         sendTimer.schedule(new TimerTask() {
             @Override
             public void run() {
                while (true) {
                   sendMessageForRandomIdentifier();
                }
             }
         }, 1000);
     }
     public void start() {
         vertx.createHttpServer().setAcceptBacklog(10000).websocketHandler(new Handler&lt;ServerWebSocket>() {
             public void handle(final ServerWebSocket ws) {
                 if (ws.path.equals("/channels")) {
                     ws.dataHandler(new Handler&lt;Buffer>() {
                         public void handle(Buffer data) {
                             try {
                                 JsonParser jp = jsonFactory.createJsonParser(data.toString());
                                 JsonNode jsonObj = mapper.readTree(jp);
                                 String action = jsonObj.get("action").asText();
                                 String identifier = jsonObj.get("identifier").asText();
                                 if (action == null || identifier == null) {
                                     return;
                                 }
                                 if ("subscribe".equals(action)) {
                                     maxIdentifier = Math.max(Integer.parseInt(identifier), maxIdentifier);
                                     logger.info("Max " + maxIdentifier);
                                     Set&lt;ServerWebSocket> sockets = subscriptions.get(identifier);
                                     if (sockets == null) {
                                         sockets = new HashSet&lt;ServerWebSocket>();
                                         subscriptions.put(identifier, sockets);
                                     }
                                     sockets.add(ws);
                                 } else if ("unsubscribe".equals(action)) {
                                     Set&lt;ServerWebSocket> sockets = subscriptions.get(identifier);
                                     if (sockets == null) {
                                         return;
                                     }
                                     sockets.remove(ws);
                                     if (sockets.size() == 0) {
                                         subscriptions.remove(identifier);
                                     }
                                 }
                             } catch (IOException e) {
                                 logger.error("Failed to handle " + data, e);
                             }
                         }
                     });
                 } else {
                     ws.reject();
                 }
             }
         }).requestHandler(new Handler&lt;HttpServerRequest>() {
             public void handle(HttpServerRequest req) {
                 //if (req.path.equals("/")) req.response.sendFile("websockets/ws.html"); // Serve the html
             }
         }).listen(8080);
     }

     //
     private String getRandomIdentifier() {
         return String.valueOf(Math.abs(random.nextInt(maxIdentifier)) + 1);
     }

     public void sendMessageForRandomIdentifier() {
         if (subscriptions.size() == 0) {
             return;
         }
         String identifier = getRandomIdentifier();
         if (identifier == null) {
             return;
         }
         Set&lt;ServerWebSocket> sockets = subscriptions.get(identifier);
         if (sockets == null || sockets.size() == 0) {
             return;
         }
         final long t = System.currentTimeMillis();
         String msg = "{\"request\":" + nextRequestId.incrementAndGet() + ", \"timestamp\":" + t + ", \"identifier\":" + identifier + ", \"elements\": \"" + elements + "\"}";
         for (ServerWebSocket ws : sockets) {
             try {
                 ws.writeTextFrame(msg);
             } catch (Exception e) {
                 subscriptions.remove(ws);
                 logger.error("Failed to send " + msg, e);
             }
         }
         metrics.update(msg);
     }
 }
</pre>
<p> Note that Vertx.io requires JDK 7 and here is how you can start the server:</p>
<pre class="brush: bash">
 export PATH=/home/sbhatti/jdk1.7.0_10/bin:$PATH
 export JAVA_HOME=/home/sbhatti/jdk1.7.0_10
 mvn package
 bin/vertx run com.plexobject.vertx.VrtxServer -cp target/classes/ -instances=5
 </pre>
<p> Note that I started Vertx.io with 5 instances of the server to match Node.js&#8217; clustering options.</p>
<h3>Netty</h3>
<p> <a href="https://netty.io/">Netty.io</a> is a lightweight application server that provides asynchronous events and I/O so I also tested its Websocket&#8217;s support in its latest 4.0-beta version. </p>
<h4>Bootstrap</h4>
<p> Here is the main server implementation that bootstraps web-socket server:</p>
<pre class="brush: java">
 package com.plexobject.netty.server;

 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.Channel;
 import io.netty.channel.socket.nio.NioEventLoopGroup;
 import io.netty.channel.socket.nio.NioServerSocketChannel;

 public class WebSocketServer {

         private final int port;

         public WebSocketServer(int port) {
                 this.port = port;
         }

         public void run() throws Exception {
                 ServerBootstrap b = new ServerBootstrap();
                 try {
                         b.group(new NioEventLoopGroup(), new NioEventLoopGroup())
                                         .channel(NioServerSocketChannel.class).localAddress(port)
                                         .childHandler(new WebSocketServerInitializer());

                         Channel ch = b.bind().sync().channel();

                         ch.closeFuture().sync();
                 } finally {
                         b.shutdown();
                 }
         }

         public static void main(String[] args) throws Exception {
                 int port;
                 if (args.length > 0) {
                         port = Integer.parseInt(args[0]);
                 } else {
                         port = 8124;
                 }
                 new WebSocketServer(port).run();
         }
 }
</pre>
<h4>Pub/Sub Server</h4>
<p> Here is implementation of websocket handler that provides pub/sub functionality:</p>
<pre class="brush: java">
 package com.plexobject.netty.server;

 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.Random;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
 import java.math.BigInteger;
 import java.security.SecureRandom;
 import java.util.concurrent.atomic.AtomicLong;

 import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
 import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
 import static io.netty.handler.codec.http.HttpMethod.GET;
 import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
 import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
 import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
 import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelFutureListener;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundMessageHandlerAdapter;
 import io.netty.handler.codec.http.DefaultHttpResponse;
 import io.netty.handler.codec.http.HttpHeaders;
 import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.HttpResponse;
 import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
 import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
 import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
 import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
 import io.netty.handler.codec.http.websocketx.WebSocketFrame;
 import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
 import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
 import io.netty.logging.InternalLogger;
 import io.netty.logging.InternalLoggerFactory;
 import io.netty.util.CharsetUtil;

 import org.codehaus.jackson.JsonFactory;
 import org.codehaus.jackson.JsonNode;
 import org.codehaus.jackson.JsonParser;
 import org.codehaus.jackson.map.ObjectMapper;

 public class WebSocketServerHandler extends ChannelInboundMessageHandlerAdapter&lt;Object> {
     @SuppressWarnings("unused")
     private static final InternalLogger LOGGER = InternalLoggerFactory
             .getInstance(WebSocketServerHandler.class);
     private static final ObjectMapper jsonMapper = new ObjectMapper();
     private static final JsonFactory jsonFactory = jsonMapper.getJsonFactory();

     private static final String WEBSOCKET_PATH = "/";
     private WebSocketServerHandshaker handshaker;
     private static Map&lt;String, Set&lt;ChannelHandlerContext>> subscriptions = new ConcurrentHashMap&lt;String, Set&lt;ChannelHandlerContext>>();
     private static Timer timer = new Timer(true);
     private static final AtomicLong REQUEST_ID = new AtomicLong();
     private static final Metrics METRICS = new Metrics();

     static {
         timer.schedule(new TimerTask() {
             @Override
             public void run() {
                while (true) {
                   sendMessageForRandomIdentifier();
                }
             }
         }, 1000);
     }

     @Override
     public void messageReceived(ChannelHandlerContext ctx, Object msg)
             throws Exception {
         if (msg instanceof HttpRequest) {
             handleHttpRequest(ctx, (HttpRequest) msg);
         } else if (msg instanceof WebSocketFrame) {
             handleWebSocketFrame(ctx, (WebSocketFrame) msg);
         }
     }

     private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req)
             throws Exception {
         // Allow only GET methods.
         if (req.getMethod() != GET) {
             sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1,
                     FORBIDDEN));
             return;
         }
         if (req.getUri().equals("/favicon.ico")) {
             HttpResponse res = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND);
             sendHttpResponse(ctx, req, res);
             return;
         }

         // Handshake
         WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                 getWebSocketLocation(req), null, false);
         handshaker = wsFactory.newHandshaker(req);
         if (handshaker == null) {
             wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
         } else {
             handshaker.handshake(ctx.channel(), req);
         }
     }

     private void handleWebSocketFrame(ChannelHandlerContext ctx,
             WebSocketFrame frame) throws IOException {
         // Check for closing frame
         if (frame instanceof CloseWebSocketFrame) {
             handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame);
             return;
         } else if (frame instanceof PingWebSocketFrame) {
             ctx.channel().write(new PongWebSocketFrame(frame.getBinaryData()));
             return;
         } else if (!(frame instanceof TextWebSocketFrame)) {
             throw new UnsupportedOperationException(String.format(
                     "%s frame types not supported", frame.getClass().getName()));
         }

         String jsonText = ((TextWebSocketFrame) frame).getText();
         JsonParser jp = jsonFactory.createJsonParser(jsonText);
         JsonNode actualObj = jsonMapper.readTree(jp);
         String action = actualObj.get("action").getTextValue();
         String identifier = actualObj.get("identifier").getTextValue();
         if (action == null || identifier == null) {
               return;
         }
         if ("subscribe".equals(action)) {
             Set&lt;ChannelHandlerContext> contexts = subscriptions.get(identifier);
             if (contexts == null) {
                 contexts = new HashSet&lt;ChannelHandlerContext>();
                 subscriptions.put(identifier, contexts);
             }
             contexts.add(ctx);
         } else if ("unsubscribe".equals(action)) {
             Set&lt;ChannelHandlerContext> contexts = subscriptions.get(identifier);
             if (contexts == null) {
                 return;
             }
             contexts.remove(ctx);
             if (contexts.size() == 0) {
                 subscriptions.remove(identifier);
             }
         }
     }

     private static String getRandomIdentifier() {
         List&lt;String> identifiers = new ArrayList&lt;String>(subscriptions.keySet());
         if (identifiers.size() == 0) {
             return null;
         }
         int n = new Random().nextInt(identifiers.size());
         return identifiers.get(n);
     }

     public static void sendMessageForRandomIdentifier() {
         String identifier = getRandomIdentifier();
         if (identifier == null) {
             return;
         }
         Set&lt;ChannelHandlerContext> contexts = subscriptions.get(identifier);
         if (contexts == null || contexts.size() == 0) {
             return;
         }
         SecureRandom random = new SecureRandom();
         String elements = new BigInteger(500, random).toString(32);
         String json = "{\"request\":" + REQUEST_ID.incrementAndGet() + ", \"timestamp\":" + System.currentTimeMillis() + ", \"identifier\":\"" + identifier + "\", \"elements\": \"" + elements + "\"}";
         TextWebSocketFrame frame = new TextWebSocketFrame(json);
         METRICS.update(json);
         for (ChannelHandlerContext ctx : contexts) {
             try {
                 ctx.channel().write(frame);
             } catch (Exception e) {
                 subscriptions.remove(ctx);
             }
         }
         if (System.currentTimeMillis() % 1000 == 0) {
             System.out.println("identifiers," + Metrics.getHeading());
             System.out.println(subscriptions.size() + METRICS.getSummary().toString());
         }
     }

     private static void sendHttpResponse(ChannelHandlerContext ctx,
             HttpRequest req, HttpResponse res) {
         // Generate an error page if response status code is not OK (200).
         if (res.getStatus().getCode() != 200) {
             res.setContent(Unpooled.copiedBuffer(res.getStatus().toString(),
                     CharsetUtil.UTF_8));
             setContentLength(res, res.getContent().readableBytes());
         }

         // Send the response and close the connection if necessary.
         ChannelFuture f = ctx.channel().write(res);
         if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
             f.addListener(ChannelFutureListener.CLOSE);
         }
     }

     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
             throws Exception {
         if (cause instanceof java.nio.channels.ClosedChannelException == false) {
             //cause.printStackTrace();
         }
         ctx.close();
     }

     private static String getWebSocketLocation(HttpRequest req) {
         return "ws://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH;
     }
 }
</pre>
<p> As, you can see its implementation is a bit verbose and I am skipping some of other boiler code.</p>
<h3>Atmosphere</h3>
<p> <a href="https://github.com/Atmosphere/atmosphere">Atmosphere is another Java framework that provides support for Websockets so I tried it as well and here is its implementation:</p>
<pre class="brush: java">
 package com.plexobject;

 import org.atmosphere.config.service.WebSocketHandlerService;
 import org.atmosphere.cpr.Broadcaster;
 import org.atmosphere.cpr.BroadcasterFactory;
 import org.atmosphere.websocket.WebSocket;
 import org.atmosphere.websocket.WebSocketHandler;
 import org.atmosphere.websocket.WebSocketProcessor.WebSocketException;

 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.core.JsonFactory;
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.databind.JsonNode;

 import java.util.Random;
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.atomic.AtomicLong;

 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.nio.SelectChannelConnector;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.atmosphere.cpr.AtmosphereServlet;
 import org.atmosphere.websocket.WebSocketHandler;

 @WebSocketHandlerService
 public class ChannelResponseHandler extends WebSocketHandler {
     private static final Logger logger = LoggerFactory.getLogger(ChannelResponseHandler.class);

     private static final Metrics METRICS = new Metrics();
     private static final String PATH_PREFIX = "/channels";
     private boolean started;
     private int maxIdentifier;
     private final Random random = new Random();
     private Timer timer = new Timer(true);
     private final ObjectMapper mapper = new ObjectMapper();
     private final JsonFactory jsonFactory = mapper.getJsonFactory();
     private final AtomicLong opened = new AtomicLong();
     private final AtomicLong closed = new AtomicLong();
     private final AtomicLong nextRequestId = new AtomicLong();
     private final StringBuilder elements = new StringBuilder();

     public ChannelResponseHandler() {
         for (int i=0; i&lt;500; i++) {
             elements.append(String.valueOf(i));
         }
     }

     @Override
     public void onTextMessage(WebSocket webSocket, String msg) {
         try {
             JsonParser jp = jsonFactory.createJsonParser(msg);
             JsonNode jsonObj = mapper.readTree(jp);
             String action = jsonObj.get("action").asText();
             String identifier = jsonObj.get("identifier").asText();
             logger.debug("onMessage " + action + " - " + identifier);
             if (action == null || identifier == null) {
                   return;
             }
             if ("subscribe".equals(action)) {
                 getBroadcaster(identifier).addAtmosphereResource(webSocket.resource());
                 maxIdentifier = Math.max(Integer.parseInt(identifier), maxIdentifier);
             } else if ("unsubscribe".equals(action)) {
                 getBroadcaster(identifier).removeAtmosphereResource(webSocket.resource());
             }
             start();
         } catch (Exception e) {
             logger.error("Failed to handle message " + msg, e);
         }
     }

     @Override
     public void onOpen(WebSocket webSocket) {
         logger.trace("onOpen {}", webSocket.resource().getRequest());
         webSocket.resource().suspend();
         opened.incrementAndGet();
     }

     @Override
     public void onClose(WebSocket webSocket) {
         logger.trace("onClose");
         getBroadcaster().removeAtmosphereResource(webSocket.resource());
         super.onClose(webSocket);
         closed.incrementAndGet();
     }

     @Override
     public void onError(WebSocket webSocket, WebSocketException t) {
         System.err.println("error " + t);
         logger.trace("onError {}", t.getStackTrace());
         super.onError(webSocket, t);
     }

     public void sendMessageForRandomIdentifier() {
         try {
             if (opened.get() &lt;= closed.get()) {
                 return;
             }
             String identifier = getRandomIdentifier();
             Broadcaster bc = getBroadcaster(identifier);
             if (bc == null) {
                 logger.debug("Failed to find broadcaster for " + identifier);
                 return;
             }
             long t = System.currentTimeMillis();
             String msg = "{\"request\":" + nextRequestId.incrementAndGet() + ", \"timestamp\":" + t + ", \"identifier\":" + identifier + ", \"elements\": \"" + elements + "\"}";
             bc.broadcast(msg);
             METRICS.update(msg);
             if (t % 1000 == 0) {
                 logger.info(METRICS.getSummary().toString());
             }
         } catch (Exception e) {
             logger.error("Failed to send message", e);
         }
     }
     private Broadcaster getBroadcaster() {
         return getBroadcaster(null);
     }
     //
     private Broadcaster getBroadcaster(String identifier) {
         String path = identifier == null ? PATH_PREFIX : PATH_PREFIX + "/" + identifier;
         BroadcasterFactory factory = BroadcasterFactory.getDefault();
         return factory != null ? factory.lookup(path, true) : null;
     }

     synchronized void start() {
         if (started) return;
         started = true;
         timer.schedule(new TimerTask() {
                 @Override
                 public void run() {
                   while (started) {
                      sendMessageForRandomIdentifier();
                   }
                 }
             }, 1000);
     }

     synchronized void stop() {
         timer.cancel();
     }

     private String getRandomIdentifier() {
         return String.valueOf(random.nextInt(maxIdentifier) + 1);
     }

     public static void main(String[] arguments) throws Exception {
         try {
             Server server = new Server();
             Connector con = new SelectChannelConnector();
             con.setPort(8124);
             server.addConnector(con);
             ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
             context.setContextPath("/");
             server.setHandler(context);
             context.addServlet(new ServletHolder(new AtmosphereServlet()), "/*");
             server.start();//WebSocketHandler
             server.join();
         } catch (Exception ex) {
             System.err.println(ex);
         }
     }
 }
</pre>
<p> Note that I had to deploy Atmosphere in <a href="http://jetty.codehaus.org/jetty/">Jetty 8.1.7</a> as a war file and here is web.xml</p>
<pre class="brush: bash">
 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
 &lt;web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
          id="WebApp_ID" version="2.5"&gt;

     &lt;display-name&gt;WebSocket Load Test&lt;/display-name&gt;
     &lt;servlet&gt;
         &lt;description&gt;AtmosphereServlet&lt;/description&gt;
         &lt;servlet-name&gt;AtmosphereServlet&lt;/servlet-name&gt;
         &lt;servlet-class&gt;org.atmosphere.cpr.AtmosphereServlet&lt;/servlet-class&gt;
         &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
     &lt;/servlet&gt;
     &lt;servlet-mapping&gt;
         &lt;servlet-name&gt;AtmosphereServlet&lt;/servlet-name&gt;
         &lt;url-pattern&gt;/channels/*&lt;/url-pattern&gt;
     &lt;/servlet-mapping&gt;
 &lt;/web-app&gt;
</pre>
<h3>Client</h3>
<p> I previously wrote client in Javascript but I rewrote in Java so that I can use Java&#8217;s concurrent library to manage counters. </p>
<h4>WebSocket Client</h4>
<p> I tried <a href="https://netty.io/">Netty</a> and <a href="http://code.google.com/p/weberknecht/">weberknecht</a> libraries and settled on weberknecht. Here is its implementation:</p>
<pre class="brush: java">
 package com.plexobject.websocket.client;

 import java.io.BufferedReader;
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.URI;
 import java.net.URL;
 import java.util.Calendar;
 import java.util.HashMap;
 import java.util.Map;
 import de.roderick.weberknecht.*;

 public class WebSocketClient implements ClientProtocol {
     private WebSocket webSocket;
         private final String identifier;

         public WebSocketClient(final String identifier, final URI server, final ClientEventListener listener) throws Exception {
                 this.identifier = identifier;

         webSocket = new WebSocketConnection(server);
         webSocket.setEventHandler(new WebSocketEventHandler() {

             @Override
             public void onClose() {
                         listener.onClose();
             }

             @Override
             public void onMessage(WebSocketMessage msg) {
                             listener.onMessage(identifier, msg.getText());
             }

             @Override
             public void onOpen() {
                         listener.onOpen();
             }
         });
         webSocket.connect();
         }

     @Override
     public void close() throws Exception {
         webSocket.close();
     }
     @Override
     public void send(String text) throws Exception {
         webSocket.send(text);
     }

     @Override
     public String getIdentifier() {
         return identifier;
     }
 }
</pre>
<h3>Kaazing</h3>
<p> I also tested <a href="http://kaazing.com/">Kaazing</a> a bit but it is a commercial software and their trial license limited connections so I gave up on it.</p>
<h4>Load Tester</h4>
<p> The load tester takes arguments about how many connections to open and how long to run the test. It generates identifiers from 1 to 10,000 and sends subscription message to the server for each of those channel identifiers. It then keeps track of messages received in a helper class Metrics and logs that information on regular interval, i.e.,</p>
<pre class="brush: java">
 package com.plexobject.websocket.client;

 import java.io.BufferedWriter;
 import java.io.FileNotFoundException;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.net.URI;
 import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;

 import java.util.concurrent.atomic.AtomicLong;

 public class LoadTester implements ClientEventListener {
     private static final AtomicLong OPEN_CONNECTIONS = new AtomicLong();
     private static final AtomicLong CLOSE_CONNECTIONS = new AtomicLong();
     private static final AtomicLong REQUEST_ID = new AtomicLong();
     private static final Metrics METRICS = new Metrics();
     private List&lt;ClientProtocol> clients = new ArrayList&lt;ClientProtocol>();
     private static final List&lt;Integer> identifiers = new ArrayList&lt;Integer>();
     static {
         for (int i=1; i&lt;=10000; i++) {
             identifiers.add(i);
         }
     }

     public LoadTester(String url, int maxConnections) throws Exception {
         try {
             URI uri = new URI(url);
             for (int i=0; i&lt;maxConnections; i++) {
                 String identifier = String.valueOf(identifiers.get(i%identifiers.size()));
                 ClientProtocol client = new WebSocketClient(identifier, uri, this);
                 String json = "{\"action\":\"subscribe\", \"identifier\":\"" + identifier + "\"}";
                 client.send(json);
                 clients.add(client);
                 try {
                     Thread.sleep(1);
                 } catch (InterruptedException e) {
                 }
             }
         } catch (OutOfMemoryError e) {
             System.err.println(e);
             System.exit(0);
         }
     }

     //
     public void close() {
         for (ClientProtocol client : clients) {
             try {
                 client.close();
             } catch (Exception e) {
             }
         }
         System.exit(0);
     }

     @Override
     public void onError(Throwable e) {
         System.err.println(e);
     }  

     @Override
     public void onMessage(String identifier, String message) {
         String act = METRICS.update(message);
         if (!identifier.equals(act)) {
             System.out.println("Expected " + identifier + ", but received " + act + " for " + message);
         }
     }

     @Override
     public void onClose() {
         CLOSE_CONNECTIONS.incrementAndGet();
     }

     @Override
     public void onOpen() {
         OPEN_CONNECTIONS.incrementAndGet();
     }
     public static void main(String[] args) throws Exception {
         if (args.length == 0) {
             System.err.println("Usage client.LoadTester url connections max-runtime-in-minutes");
             System.exit(1);
         }
         final String url = args[0]; // "ws://localhost:8124/"
         final int maxConnections = Integer.parseInt(args[1]);
         final int maxRuntimeMins = Integer.parseInt(args[2]);
         System.out.println("connections,connected," + Metrics.getHeading());
         final long started = System.currentTimeMillis();
         final Timer timer = new Timer(true);
         timer.schedule(new TimerTask() {
                 @Override
                 public void run() {
                     System.out.println(maxConnections + "," + (OPEN_CONNECTIONS.get()-CLOSE_CONNECTIONS.get()) + "," + METRICS.getSummary());
                     long elapsed = System.currentTimeMillis() - started;
                     if (elapsed > (maxRuntimeMins * 1000 * 60)) {
                         System.out.println("time done");
                         System.exit(0);
                     }
                 }
             }, 5000, 15000);
         //
         LoadTester runner = new LoadTester(url, maxConnections);
         while (true) {
             try {
                 Thread.sleep(5000);
             } catch (InterruptedException e) {
             }
             //
             if (CLOSE_CONNECTIONS.get() == maxConnections) {
                 System.out.println("all clients closed");
                 runner.close();
             }
         }
     }

 }
</pre>
<p> I am skipping some of helper classes and interfaces, but you can find them from the <a href="/nodejs_cmp.zip">source code</a>.</p>
<h3>Test Script</h3>
<p> I created a simple shell script to launch load test for 5 minutes and given number of connections and collected results in a comma delimited file, i.e,</p>
<pre class="brush: bash">
 #!/bin/bash
 mvn package
 export MAVEN_OPTS="-Xmx1000m -Xss128k"
 export url=ws://localhost:8080/atmosphere-1.0/channels
 #export url=ws://localhost:8080/channels
 #export url=ws://localhost:8124/
 clients=500
 while [ $clients -lt 100000 ]
 do
    mvn exec:java -Dexec.mainClass="com.plexobject.websocket.client.LoadTester" -Dexec.classpathScope=runtime -Dexec.args="$url $clients 5"
    clients=`expr $clients + 500|awk '{print $1}'`
 done
</pre>
<p> Note that I specified stack size of thread to be 128k as default stack size per thread is 2MB, which takes a lot of memory.</p>
<h3>TCP Settings</h3>
<p> As I mentioned in my last blog, I made some changes to my Linux machine to improve TCP settings, i.e.,</p>
<pre class="brush: bash">
 net.core.rmem_max = 33554432
 net.core.wmem_max = 33554432
 net.ipv4.tcp_rmem = 4096 16384 33554432
 net.ipv4.tcp_wmem = 4096 16384 33554432
 net.ipv4.tcp_mem = 786432 1048576 26777216
 net.ipv4.tcp_max_tw_buckets = 360000
 net.core.netdev_max_backlog = 2500
 vm.min_free_kbytes = 65536
 vm.swappiness = 0
 net.ipv4.ip_local_port_range = 1024 65535
 </pre>
<p> I then applied above changes using:</p>
<pre class="brush: bash">
 sudo sysctl -w net.core.somaxconn=10000
 sudo sysctl -w net.ipv4.tcp_max_syn_backlog=10000
 sudo sysctl -p
 </pre>
<h3>Results</h3>
<p> Here are results of testing up to 5000 connections, where server sends a message to a random channel continuously:</p>
<h4>Atmosphere</h4>
<table>
<tbody>
<tr>
<th> connections </th>
<th> connected </th>
<th> average response (ms) </th>
<th> average size </th>
<th> messages </th>
<th> msg/sec </th>
<th> walltime(secs) </th>
</tr>
<tr>
<td> 500 </td>
<td> 500 </td>
<td> 1 </td>
<td> 1462 </td>
<td> 282431 </td>
<td> 943.317 </td>
<td> 301 </td>
</tr>
<tr>
<td> 1000 </td>
<td> 1000 </td>
<td> 1 </td>
<td> 1462 </td>
<td> 282100 </td>
<td> 940.967 </td>
<td> 302 </td>
</tr>
<tr>
<td> 1500 </td>
<td> 1500 </td>
<td> 1 </td>
<td> 1463 </td>
<td> 274156 </td>
<td> 920.233 </td>
<td> 304 </td>
</tr>
<tr>
<td> 2000 </td>
<td> 2000 </td>
<td> 1 </td>
<td> 1463 </td>
<td> 268180 </td>
<td> 936.733 </td>
<td> 309 </td>
</tr>
<tr>
<td> 2500 </td>
<td> 2500 </td>
<td> 1 </td>
<td> 1463 </td>
<td> 263443 </td>
<td> 926 </td>
<td> 307 </td>
</tr>
<tr>
<td> 3000 </td>
<td> 2985 </td>
<td> 1 </td>
<td> 1463 </td>
<td> 280809 </td>
<td> 932.233 </td>
<td> 332 </td>
</tr>
<tr>
<td> 3500 </td>
<td> 3500 </td>
<td> 2 </td>
<td> 1463 </td>
<td> 278412 </td>
<td> 934.333 </td>
<td> 384 </td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p> <img src="/images/atmosphere_msgs.png"></p>
<p>&nbsp;</p>
<p> <img src="/images/atmosphere_tps.png"></p>
<p>&nbsp;</p>
<h4>Netty</h4>
<table>
<tbody>
<tr>
<th> connections </th>
<th> connected </th>
<th> average response (ms) </th>
<th> average size </th>
<th> messages </th>
<th> msg/sec </th>
<th> walltime(secs) </th>
</tr>
<tr>
<td> 500 </td>
<td> 500 </td>
<td> 0.000 </td>
<td> 172.000 </td>
<td> 284165 </td>
<td> 937.833 </td>
<td> 305.000 </td>
</tr>
<tr>
<td> 1000 </td>
<td> 1000 </td>
<td> 0.000 </td>
<td> 172.000 </td>
<td> 279883 </td>
<td> 921.717 </td>
<td> 305.000 </td>
</tr>
<tr>
<td> 1500 </td>
<td> 1500 </td>
<td> 0.000 </td>
<td> 173.000 </td>
<td> 290317 </td>
<td> 959.983 </td>
<td> 305.000 </td>
</tr>
<tr>
<td> 2000 </td>
<td> 2000 </td>
<td> 0.000 </td>
<td> 173.000 </td>
<td> 286110 </td>
<td> 946.467 </td>
<td> 305.000 </td>
</tr>
<tr>
<td> 2500 </td>
<td> 2500 </td>
<td> 0.000 </td>
<td> 174.000 </td>
<td> 285122 </td>
<td> 949.550 </td>
<td> 305.000 </td>
</tr>
<tr>
<td> 3000 </td>
<td> 3000 </td>
<td> 0.000 </td>
<td> 174.000 </td>
<td> 281650 </td>
<td> 939.150 </td>
<td> 305.000 </td>
</tr>
<tr>
<td> 3500 </td>
<td> 3500 </td>
<td> 0.000 </td>
<td> 174.000 </td>
<td> 282833 </td>
<td> 942.700 </td>
<td> 305.000 </td>
</tr>
<tr>
<td> 4000 </td>
<td> 4000 </td>
<td> 0.000 </td>
<td> 174.000 </td>
<td> 283829 </td>
<td> 948.700 </td>
<td> 305.000 </td>
</tr>
<tr>
<td> 4500 </td>
<td> 4500 </td>
<td> 0.000 </td>
<td> 174.000 </td>
<td> 273293 </td>
<td> 921.583 </td>
<td> 305.000 </td>
</tr>
<tr>
<td>5000</td>
<td>5000</td>
<td>0.000</td>
<td>174.000</td>
<td>264433</td>
<td>881.733</td>
<td>305.000</td>
</tr>
<tr>
<td>1000</td>
<td>1000</td>
<td>0.000</td>
<td>172.000</td>
<td>279883</td>
<td>921.717</td>
<td>305.000</td>
</tr>
<tr>
<td>1500</td>
<td>1500</td>
<td>0.000</td>
<td>173.000</td>
<td>290317</td>
<td>959.983</td>
<td>305.000</td>
</tr>
<tr>
<td>2000</td>
<td>2000</td>
<td>0.000</td>
<td>173.000</td>
<td>286110</td>
<td>946.467</td>
<td>305.000</td>
</tr>
<tr>
<td>2500</td>
<td>2500</td>
<td>0.000</td>
<td>174.000</td>
<td>285122</td>
<td>949.550</td>
<td>305.000</td>
</tr>
<tr>
<td>3000</td>
<td>3000</td>
<td>0.000</td>
<td>174.000</td>
<td>281650</td>
<td>939.150</td>
<td>305.000</td>
</tr>
<tr>
<td>3500</td>
<td>3500</td>
<td>0.000</td>
<td>174.000</td>
<td>282833</td>
<td>942.700</td>
<td>305.000</td>
</tr>
<tr>
<td>4000</td>
<td>4000</td>
<td>0.000</td>
<td>174.000</td>
<td>283829</td>
<td>948.700</td>
<td>305.000</td>
</tr>
<tr>
<td>4500</td>
<td>4500</td>
<td>0.000</td>
<td>174.000</td>
<td>273293</td>
<td>921.583</td>
<td>305.000</td>
</tr>
<tr>
<td>5000</td>
<td>5000</td>
<td>0.000</td>
<td>174.000</td>
<td>264433</td>
<td>881.733</td>
<td>305.000</td>
</tr>
<tr>
<td>5500</td>
<td>5500</td>
<td>0.000</td>
<td>174.000</td>
<td>289230</td>
<td>977.300</td>
<td>305.000</td>
</tr>
<tr>
<td>6000</td>
<td>6000</td>
<td>0.000</td>
<td>174.000</td>
<td>277692</td>
<td>934.800</td>
<td>305.000</td>
</tr>
<tr>
<td>6500</td>
<td>6500</td>
<td>0.000</td>
<td>174.000</td>
<td>148223</td>
<td>847.517</td>
<td>185.000</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p> <img src="/images/netty_msgs.png"></p>
<p>&nbsp;</p>
<p> <img src="/images/netty_tps.png"></p>
<p>&nbsp;</p>
<h4>Vertx.io</h4>
<table>
<tbody>
<tr>
<th>connections</th>
<th>average response (ms)</th>
<th> average size</th>
<th> messages</th>
<th> msg/sec</th>
<th> walltime(secs)</th>
</tr>
<tr>
<td>1000</td>
<td>1</td>
<td>1462</td>
<td>1487141</td>
<td>4056.433</td>
<td>365</td>
</tr>
<tr>
<td>2000</td>
<td>1</td>
<td>1464</td>
<td>1607431</td>
<td>5003.033</td>
<td>365</td>
</tr>
<tr>
<td>3000</td>
<td>1</td>
<td>1464</td>
<td>1663777</td>
<td>5061.717</td>
<td>365</td>
</tr>
<tr>
<td>4000</td>
<td>1</td>
<td>1464</td>
<td>1607104</td>
<td>5109.65</td>
<td>365</td>
</tr>
<tr>
<td>5000</td>
<td>1</td>
<td>1464</td>
<td>1706898</td>
<td>5374.433</td>
<td>365</td>
</tr>
<tr>
<td>6000</td>
<td>2</td>
<td>1465</td>
<td>1750757</td>
<td>5341.05</td>
<td>365</td>
</tr>
<tr>
<td>7000</td>
<td>3</td>
<td>1465</td>
<td>1723124</td>
<td>5263.283</td>
<td>365</td>
</tr>
<tr>
<td>8000</td>
<td>3</td>
<td>1465</td>
<td>1758174</td>
<td>5224.85</td>
<td>365</td>
</tr>
<tr>
<td>9000</td>
<td>2</td>
<td>1465</td>
<td>1717950</td>
<td>5038.45</td>
<td>365</td>
</tr>
<tr>
<td>10000</td>
<td>3</td>
<td>1465</td>
<td>1789671</td>
<td>5160.517</td>
<td>365</td>
</tr>
<tr>
<td>11000</td>
<td>3</td>
<td>1465</td>
<td>1789760</td>
<td>5140.8</td>
<td>365</td>
</tr>
<tr>
<td>12000</td>
<td>3</td>
<td>1465</td>
<td>1792477</td>
<td>5097.5</td>
<td>365</td>
</tr>
<tr>
<td>13000</td>
<td>4</td>
<td>1465</td>
<td>1778748</td>
<td>5128.917</td>
<td>365</td>
</tr>
<tr>
<td>14000</td>
<td>4</td>
<td>1465</td>
<td>1746830</td>
<td>4895.433</td>
<td>365</td>
</tr>
<tr>
<td>15000</td>
<td>3</td>
<td>1465</td>
<td>1760734</td>
<td>4960.367</td>
<td>365</td>
</tr>
<tr>
<td>16000</td>
<td>3</td>
<td>1465</td>
<td>1739786</td>
<td>4933.583</td>
<td>365</td>
</tr>
<tr>
<td>17000</td>
<td>4</td>
<td>1465</td>
<td>1727759</td>
<td>4902.883</td>
<td>365</td>
</tr>
<tr>
<td>18000</td>
<td>3</td>
<td>1465</td>
<td>1730165</td>
<td>5158.7</td>
<td>365</td>
</tr>
<tr>
<td>19000</td>
<td>4</td>
<td>1465</td>
<td>1725660</td>
<td>4904.2</td>
<td>367</td>
</tr>
<tr>
<td>20000</td>
<td>4</td>
<td>1465</td>
<td>1704129</td>
<td>4852.567</td>
<td>365</td>
</tr>
<tr>
<td>21000</td>
<td>4</td>
<td>1465</td>
<td>1703201</td>
<td>4849.833</td>
<td>365</td>
</tr>
<tr>
<td>22000</td>
<td>3</td>
<td>1465</td>
<td>1705387</td>
<td>5077.417</td>
<td>366</td>
</tr>
<tr>
<td>23000</td>
<td>3</td>
<td>1465</td>
<td>1671165</td>
<td>4861.233</td>
<td>365</td>
</tr>
<tr>
<td>24000</td>
<td>4</td>
<td>1465</td>
<td>1670471</td>
<td>4942.217</td>
<td>365</td>
</tr>
<tr>
<td>25000</td>
<td>4</td>
<td>1465</td>
<td>1639296</td>
<td>5108.317</td>
<td>365</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p> <img src="/images/vrtx_msgs.png"></p>
<p>&nbsp;</p>
<p> <img src="/images/vrtx_tps.png"></p>
<p>&nbsp;</p>
<h4>Node.js</h4>
<table>
<tbody>
<tr>
<th> connections </th>
<th> average response (ms) </th>
<th> average size </th>
<th> messages </th>
<th> msg/sec </th>
<th> walltime(secs) </th>
</tr>
<tr>
<td>1000</td>
<td>2</td>
<td>1363</td>
<td>1550155</td>
<td>4252.95</td>
<td>365</td>
</tr>
<tr>
<td>2000</td>
<td>1</td>
<td>1368</td>
<td>863997</td>
<td>2384.8</td>
<td>365</td>
</tr>
<tr>
<td>3000</td>
<td>1</td>
<td>1368</td>
<td>644806</td>
<td>1776.717</td>
<td>365</td>
</tr>
<tr>
<td>4000</td>
<td>1</td>
<td>1369</td>
<td>555496</td>
<td>1541.683</td>
<td>365</td>
</tr>
<tr>
<td>5000</td>
<td>1</td>
<td>1370</td>
<td>483624</td>
<td>1338</td>
<td>365</td>
</tr>
<tr>
<td>6000</td>
<td>1</td>
<td>1370</td>
<td>426728</td>
<td>1185.883</td>
<td>365</td>
</tr>
<tr>
<td>7000</td>
<td>1</td>
<td>1371</td>
<td>358331</td>
<td>994.017</td>
<td>365</td>
</tr>
<tr>
<td>8000</td>
<td>1</td>
<td>1371</td>
<td>322960</td>
<td>898.55</td>
<td>365</td>
</tr>
<tr>
<td>9000</td>
<td>1</td>
<td>1371</td>
<td>281227</td>
<td>787.633</td>
<td>365</td>
</tr>
<tr>
<td>10000</td>
<td>1</td>
<td>1371</td>
<td>260502</td>
<td>732.3</td>
<td>365</td>
</tr>
<tr>
<td>11000</td>
<td>1</td>
<td>1370</td>
<td>262209</td>
<td>733.633</td>
<td>365</td>
</tr>
<tr>
<td>12000</td>
<td>1</td>
<td>1371</td>
<td>243954</td>
<td>690.067</td>
<td>365</td>
</tr>
<tr>
<td>13000</td>
<td>2</td>
<td>1370</td>
<td>230117</td>
<td>647.833</td>
<td>365</td>
</tr>
<tr>
<td>14000</td>
<td>2</td>
<td>1371</td>
<td>235591</td>
<td>668.983</td>
<td>365</td>
</tr>
<tr>
<td>15000</td>
<td>2</td>
<td>1371</td>
<td>240632</td>
<td>685.35</td>
<td>365</td>
</tr>
<tr>
<td>16000</td>
<td>2</td>
<td>1371</td>
<td>192929</td>
<td>551.083</td>
<td>365</td>
</tr>
<tr>
<td>17000</td>
<td>2</td>
<td>1371</td>
<td>166705</td>
<td>474.217</td>
<td>365</td>
</tr>
<tr>
<td>18000</td>
<td>2</td>
<td>1371</td>
<td>142557</td>
<td>406.4</td>
<td>365</td>
</tr>
<tr>
<td>19000</td>
<td>2</td>
<td>1371</td>
<td>110873</td>
<td>319.467</td>
<td>365</td>
</tr>
<tr>
<td>20000</td>
<td>2</td>
<td>1371</td>
<td>106047</td>
<td>305.967</td>
<td>365</td>
</tr>
<tr>
<td>21000</td>
<td>2</td>
<td>1371</td>
<td>107947</td>
<td>307.633</td>
<td>365</td>
</tr>
<tr>
<td>22000</td>
<td>1</td>
<td>1371</td>
<td>89779</td>
<td>257.883</td>
<td>365</td>
</tr>
<tr>
<td>23000</td>
<td>2</td>
<td>1371</td>
<td>89890</td>
<td>259.367</td>
<td>365</td>
</tr>
<tr>
<td>24000</td>
<td>2</td>
<td>1371</td>
<td>34414</td>
<td>240.833</td>
<td>155</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p> <img src="/images/nodejs_msgs.png"></p>
<p>&nbsp;</p>
<p> <img src="/images/nodejs_tps.png"></p>
<p>&nbsp;</p>
<h3>Summary</h3>
<p> I tried to push limits for each framework, however Atmosphere could not handle more than 3500 connections and it slowed my machine to crawl. I was using 4.0-beta version of Netty, which also crashed after 6500 connections. Vertx.io was able to scale for upto 25,000 connections, but then server ran out of memory on my machine. Similarly, node.js kept going for about 24,000 but it became very slow and my load test client could not allocate more memory for connections. Both Vertx.io and Node.js proved to be leading contenders but Vertx.io was able to scale much better and maintain throughput better than Node.js. As a result, I picked Vertx.io for my project. You can download the <a href="/nodejs_cmp.zip">source code</a> and compare results yourself.</p>
<p> <script type="text/javascript">
      SyntaxHighlighter.all()
 </script> </p>
<div style='background-color: white'>
<div id="fb-root">
 </div>
<p> <script>
      window.fbAsyncInit = function() {
           FB.init({appId: '119853371386770', status: true, cookie: true, xfbml: true});
         };
         (function() {
           var e = document.createElement('script'); e.async = true;
           e.src = document.location.protocol +
             '//connect.facebook.net/en_US/all.js';
           document.getElementById('fb-root').appendChild(e);
         }());
 </script></p>
<div id="facebook">
   <fb:comments xid="ScalingNodejs" simple="1" publish_feed="false" width="642" title='a comment on "Scaling Node.js and Websockets"'><br />
   </fb:comments>
 </div>
</p></div>
]]></content:encoded>
			<wfw:commentRss>http://weblog.plexobject.com/?feed=rss2&amp;p=1698</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Scaling Node.js</title>
		<link>http://weblog.plexobject.com/?p=1697</link>
		<comments>http://weblog.plexobject.com/?p=1697#comments</comments>
		<pubDate>Mon, 24 Sep 2012 12:22:53 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Javascript]]></category>

		<guid isPermaLink="false">http://weblog.plexobject.com/?p=1697</guid>
		<description><![CDATA[As I described in my earlier <a href="http://weblog.plexobject.com/?p=1695">blog</a>, I have been testing Node.js and Websockets for streaming data to large number of clients. I am evaluating Node.js and WebSockets in both pub/sub model such as clients co]]></description>
			<content:encoded><![CDATA[<p>As I described in my earlier <a href="http://weblog.plexobject.com/?p=1695">blog</a>, I have been testing Node.js and Websockets for streaming data to large number of clients. I am evaluating Node.js and WebSockets in both pub/sub model such as clients connecting to receive streaming quotes and request/reply model where clients are invoking an API and waiting for reply. Below are some of my experience with those technologies so far:</p>
<h3>Pub/Sub Server</h3>
<p> The server code uses cluster module to provide multiprocessing support and each process starts a Websocket server and listens for incoming connections. I used <a href="http://socket.io/">socket.io</a> library for WebSockets. The server keeps track of all clients that have subscribed and then send random data every 10-100 milli-seconds.<br />
 Here is basic implementation of the server:</p>
<pre class="brush: javascript">
 var MAX_THREADS = process.env.MAX_THREADS || 10;
 var MAX_ROWS = process.env.MAX_ROWS || 2;
 var nextClientId = 0;
 //
 var helper = require('helper')

 var pubsubController = require('pubsub_controller')(MAX_ROWS)
 var cluster = require('cluster');

 if (cluster.isMaster) {
    for (var i = 0; i < MAX_THREADS; i++) {
       cluster.fork();
    }
    cluster.on('exit', function(worker, code, signal) {
       console.log('worker ' + worker.process.pid + ' died');
    });
 } else {
    var socketsByAddr = {};
    var io = require('socket.io').listen(8124);
    io.set('log level', 1);
    io.set('transports', ['websocket']);
    io.set('force new connection', true);
    io.enable('browser client gzip');

    io.sockets.on('connection', function(socket) {
       socket.address = socket.handshake.address.address + ':' + socket.handshake.address.port;
       socket.key = socket.handshake.address.address + ':' + socket.handshake.address.port + ':' + (++nextClientId);
       socketsByAddr[socket.address] = true;
       pubsubController.subscribe(socket);
       socket.on('disconnect', function() {
          pubsubController.unsubscribe(socket);
          delete socketsByAddr[socket.address];
       });

       socket.on('end', function() {
       });
    });

    setInterval(function() {
       pubsubController.pushUpdates();
    }, helper.random(100) + 10);
 }

 process.on('uncaughtException', function(err) {
   console.log("GLOBAL " + err + "\n" + err.stack);
 });
</pre>
<h3>Pub/Sub Client</h3>
<p> The pub/sub client subscribes for data and receies data every 10-100 milli-seconds. The client code builds up 100 more connections every 15 seconds and it logs the response time information.  On client side, I used <a href="https://github.com/LearnBoost/socket.io-client">socket.io-client</a> library. The client calls 'subscribe-execs' to subscribe and then receive messages under 'receive-execs'. Upon receiving message, the client tracks various metrics such as response time, CPU, memory usage, etc. As, I am running both client and server on the same machine, it gives accurate representation of latency without network hops or clocks mismatch. Another thing to note is that the client passes 'force new connection' flag to force new socket for each client as by default all clients share same socket.</p>
<pre class="brush: javascript">
 var OUTLIER_SIZE = process.env.OUTLIER_SIZE || 10;

 var hwUsage = require('hardware_usage');
 var metrics = require('metrics')({}, OUTLIER_SIZE);
 hwUsage.start();

 var client = require('pubsub_client')(metrics, hwUsage);
 var clients = [];

 // create connections for this client
 var buildClients = function() {
    for (var i=0; i&lt;100; i++) {
       var client = client.newClient(i+0);
       clients.push(client);
    }
    hwUsage.stop(function(usage) {
       console.log('clients,connected,', hwUsage.heading() + ',' + metrics.heading());
       console.log(clients.length + ',' + client.totalConnected() + ',' + usage.toString() + ',' + metrics.summary().toString());
    });
 };

 setInterval(buildClients, 15000);

 process.on('uncaughtException', function(err) {
   console.log("GLOBAL " + err + "\n" + err.stack);
 });
</pre>
<p> The actual client is defined as a module, e.g.:</p>
<pre class="brush: javascript">
 var io = require('socket.io-client');
 var helper = require('helper');
 var totalConnected = 0;

 module.exports = function(metrics, hwUsage) {
    var ExecClient = function(id) {
       this.id = id;
       this.client = io.connect('localhost', { port: 8124,
                               transports: ['websocket'],
                               'force new connection': true,
                               'sync on disconnect' : true,
                               'connect timeout': 500,
                               'reconnect': true,
                               'reconnection delay': 500,
                               'reopen delay': 500,
                               'max reconnection attempts': 5});
    };

    //
    ExecClient.prototype.setupConnectListener = function() {
       if (typeof this.client === "undefined") return;
       var that = this;
       this.client.on('connect_failed', function(error) {
       });

       this.client.on('connect', function() {
          totalConnected++;
       });

       this.client.on('disconnect', function() {
          totalConnected--;
       });

       this.client.on('reconnect_failed', function() {
          //process.exit(1);
       });

       this.client.on('end', function() {
          //process.exit(1);
       });
    };

    ExecClient.prototype.setupReceiveListener = function() {
       if (typeof this.client === "undefined") return;
       var that = this;
       this.client.on('receive-execs', function(msg) {
          console.log('receive');
          var metric = metrics.update(msg.address, msg.request, msg.timestamp, JSON.stringify(msg).length);
        });
    };

    return {
       totalConnected : function() {
          return totalConnected;
       },
       newClient: function(id) {
          var client = new ExecClient(id);
          client.setupConnectListener();
          client.setupReceiveListener();
          return client;
       }
    };
 }
</pre>
<h3>Subscription management</h3>
<p> Following module defines APIs for managing subscriptions and as I mentioned when number of messages received by client reaches maxMessages, it unsubscribes from the server.</p>
<pre class="brush: javascript">
 var helper = require('helper');
 var emitVolatile = typeof process.env.EMIT_VOLATILE !== "undefined" &#038;&#038; process.env.EMIT_VOLATILE === 'true';

 module.exports = function(numExecs) {
    var execSubscribers = {};
    var counter = 0;
    var generatePayload = function(address) {
       var execs = [];
       var date = new Date();
       for (var i=0; i&lt;numExecs; i++) {
          execs.push({sequence:i, activityDateStr:date.toString(), com: helper.random(5),price: helper.random(100), accountId:1772139, symbol:"QQQ", transaction:"STO", description:"QQQ Stock", qty: helper.random(200), key: "QQQ:::S", netAmount: helper.random(1000)});
       }
       var payload = {request: counter, rows:numExecs, timestamp : date.getTime(), address:address, execs: execs};
       return payload;
    }

    return {
       subscribe: function(socket) {
          execSubscribers[socket.key] = socket;
       },
       unsubscribe: function(socket) {
          delete execSubscribers[socket.key];
       },
       count: function() {
          return helper.size(execSubscribers);
       },
       pushUpdates: function() {
          var count = 0;
          for (var addr in execSubscribers) {
             count++;
             try {
                var socket = execSubscribers[addr];
                //
                if (emitVolatile) {
                   socket.volatile.emit('receive-execs', generatePayload(addr));
                } else {
                   socket.emit('receive-execs', generatePayload(addr));
                }
                //
                if (typeof socket.numMessages === "undefined") {
                   socket.numMessages = 1;
                } else {
                   ++socket.numMessages;
                   //delete execSubscribers[socket.key];
                }
             } catch (err) {
                console.log('failed to send execution report' + err);
                try {
                   socket.disconnect();
                } catch (ex) {
                   console.log('failed to close socket ' + ex);
                }
                delete execSubscribers[socket.key];
             }
          }
          return count;
       }
    };
 }
 </pre>
<h3>Hardware Measurement</h3>
<p> Following module defines API for measuring CPU time, load average, memory, etc:</p>
<pre class="brush: javascript">
 var fs = require('fs');
 var os = require('os');
 var helper = require('helper');

 var HardwareUsage = function() {
    this.startCpuTime = 0;
    this.startLoadAvg = os.loadavg();
    this.startMemory = process.memoryUsage();
    this.endCpuTime = 0;
    this.endLoadAvg = 0;
    this.endMemory = 0;
    this.started = new Date().getTime();
    var that = this;
    this.cpuUsage (function(totalCpu) {
       that.startCpuTime = totalCpu;
    });
 }

 HardwareUsage.prototype.cpuUsage = function(callback) {
    if (!fs.existsSync("/proc/" + process.pid + "/stat")) {
       callback();
       return;
    }
    var that = this;

    fs.readFile("/proc/" + process.pid + "/stat", function(err, data){
       var elems = data.toString().split(' ');
       var utime = parseInt(elems[13]);
       var stime = parseInt(elems[14]);
       var totalCpu = utime + stime;
       callback(totalCpu);
    });
 };

 HardwareUsage.prototype.percCpuChange = function() {
    return helper.percentChange(this.startCpuTime, this.endCpuTime);
 }

 HardwareUsage.prototype.percLoadChange = function() {
    return helper.percentChange(this.startLoadAvg[0], this.endLoadAvg[0]);
 }

 HardwareUsage.prototype.percMemoryChange = function() {
    return helper.percentChange(this.startMemory.heapTotal, this.endMemory.heapUsed);
 }

 HardwareUsage.prototype.toString = function() {
    return helper.round(this.endCpuTime) + ',' + helper.round(this.endLoadAvg[0]) + ',' + helper.round(this.startMemory.heapTotal/1024.0/1024.0) + ',' + helper.round(this.endMemory.heapUsed/1024.0/1024.0);
 };

 HardwareUsage.prototype.stop = function(callback) {
    var that = this;
    this.cpuUsage (function(totalCpu) {
       that.endCpuTime = totalCpu;
       that.endMemory = process.memoryUsage();
       that.endLoadAvg = os.loadavg();
       that.elapsed = new Date().getTime() - that.started;
       callback(that);
    });
 };

 var usage = new HardwareUsage();

 exports.heading = function() {
    return 'cpu time, load avg, total memory (M), used memory(M)';
 };
 exports.start = function() {
    usage = new HardwareUsage();
 };

 exports.stop = function(callback) {
    usage.stop(callback);
 };
 </pre>
<h3>Response time Measurement</h3>
<p> Following module defines API for measuring response time:</p>
<pre class="brush: javascript">
 var helper = require('helper');

 module.exports = function(store, outlierSize) {
    var MetricResultSummary = function(metric) {
       this.averageResponseTime = helper.round(metric.totalTime / metric.totalRequests);
       this.averageHighResponseTime = helper.round(helper.sum(metric.topTenHigh)/ metric.topTenHigh.length);
       this.averageLowResponseTime = helper.round(helper.sum(metric.topTenLow)/ metric.topTenLow.length);
       this.averageSize = helper.round(metric.totalPayloadSize / metric.totalRequests);
       this.messagesReceived = metric.messagesReceived;
       this.count = 0;
    }
    MetricResultSummary.prototype.toString = function() {
       return this.count + ',' + this.averageResponseTime + ',' + this.averageHighResponseTime + ',' + this.averageLowResponseTime + ',' + this.averageSize + ',' + this.messagesReceived;
    };

    var Metric = function() {
       this.totalTime = 0;
       this.totalRequests = 0;
       this.topTenHigh = [];
       this.topTenLow = [];
       this.totalPayloadSize = 0;
       this.messagesReceived = 0;
    }

    Metric.prototype.addTo = function(target) {
       target.totalTime += this.totalTime;
       target.totalRequests += this.totalRequests;
       for (var i=0; i&lt;this.topTenHigh.length; i++) {
          target.topTenHigh.push(this.topTenHigh[i]);
       }
       for (var i=0; i&lt;this.topTenLow.length; i++) {
          target.topTenLow.push(this.topTenLow[i]);
       }
       target.totalPayloadSize += this.totalPayloadSize;
       target.messagesReceived += this.messagesReceived;
    };

    Metric.prototype.update = function(id, sendTime, payloadSize) {
       var elapsed = new Date().getTime() - sendTime;
       for (var i=0; i&lt;outlierSize; i++) {
          if (typeof this.topTenHigh[i] === "undefined" || elapsed > this.topTenHigh[i]) {
             this.topTenHigh[i] = elapsed;
             break;
          }
       }
       //
       for (var i=0; i&lt;outlierSize; i++) {
          if (typeof this.topTenLow[i] === "undefined" || elapsed &lt; this.topTenLow[i]) {
             this.topTenLow[i] = elapsed;
             break;
          }
       }
       this.messagesReceived++;
       this.totalTime += elapsed;
       this.totalPayloadSize += payloadSize;
       this.totalRequests++;
    };
    //
    Metric.prototype.averageResponse = function() {
       return helper.round(this.totalTime / this.totalRequests);
    };
    Metric.prototype.summary = function() {
       return new MetricResultSummary(this);
    };
    Metric.prototype.toString = function() {
       return new MetricResultSummary(this).toString();
    };

    var get = function(key) {
          var metric = store[key];
          if (typeof metric === "undefined") {
             metric = new Metric();
             store[key] = metric;
          }
          return metric;
    };
    return {
       get: function(key) {
          return get(key);
       },
       update: function(key, id, sendTime, payloadSize) {
          var metric = get(key);
          metric.update(id, sendTime, payloadSize);
          return metric;
       },
       summaryFor: function(key) {
          var metric = get(key);
          return metric.summary();
       },
       heading: function() {
          return 'count, average response (ms), average outlier high response (ms), average outlier low response (ms), average message size, messages received';
       },
       summary: function() {
          var result = new Metric();
          var count = 0;
          for (var key in store) {
             var metric = store[key];
             metric.addTo(result);
             count++;
          }
          var summary = result.summary();
          summary.count = count;
          return summary;
       }
    };
 }
</pre>
<h3>Request/Reply Server</h3>
<p> The request/reply server also uses cluster module and listens for Websocket port on each process. Upon receiving order-request API, it sends a reply to the client. Here is the implementation of server:</p>
<pre class="brush: bash">
 var OUTLIER_SIZE = process.env.OUTLIER_SIZE || 2;
 var MAX_THREADS = process.env.MAX_THREADS || 20;
 var helper = require('helper')

 var cluster = require('cluster');
 var hwUsage = require('hardware_usage');
 var metrics = require('metrics')({}, OUTLIER_SIZE);
 hwUsage.start();
 var socketsByAddr = {};
 var totalClients = 0;

 if (cluster.isMaster) {
    for (var i = 0; i < MAX_THREADS; i++) {
       cluster.fork();
    }
    cluster.on('exit', function(worker, code, signal) {
       console.log('worker ' + worker.process.pid + ' died');
    });
 } else {
    //
    var io = require('socket.io').listen(8124);
    io.set('log level', 1);
    io.set('transports', ['websocket']);
    io.set('force new connection', true);
    io.enable('browser client gzip');

    io.sockets.on('connection', function(socket) {
       totalClients++;
       socket.address = socket.handshake.address.address + ':' + socket.handshake.address.port;
       socketsByAddr[socket.address] = true;
       socket.on('order-request', function(request) {
          var response = {request: request.counter, timestamp : request.timestamp, address:request.address};
          metrics.update(request.address, request.request, request.timestamp, JSON.stringify(request).length);
          socket.emit('order-response', response);
       });

       // disconnected
       socket.on('disconnect', function() {
          totalClients--;
          delete socketsByAddr[socket.address];
       });

       socket.on('end', function() {
       });
    });
    });
 }

 setInterval(function() {
    hwUsage.stop(function(usage) {
      console.log('sockets, clients, ' + hwUsage.heading() + ',' + metrics.heading());
      console.log(helper.size(socketsByAddr) + ',' + totalClients + ',' + usage.toString() + ',' + metrics.summary().toString());
    });
 }, 15000);

 process.on('uncaughtException', function(err) {
   console.log("GLOBAL " + err + "\n" + err.stack);
 });
</pre>
<h3>Request/Reply Client</h3>
<p> The request/reply client is similar to pub/sub model except it initiates order-request API every 50-250 milli-seconds. It also creates new connections every 15 seconds and logs response time continuously.</p>
<pre class="brush: bash">
 var OUTLIER_SIZE = process.env.OUTLIER_SIZE || 10;
 var INTERVAL_BETWEEN_NEW_CLIENTS_SECS = (process.env.INTERVAL_BETWEEN_NEW_CLIENTS_SECS || 15) * 1000;

 var hwUsage = require('hardware_usage');
 var metrics = require('metrics')({}, OUTLIER_SIZE);
 hwUsage.start();

 var order_client = require('order_client')(metrics, hwUsage);
 // create connections for this client
 var totalConnections = 0;
 var buildClients = function() {
    for (var i=0; i&lt;100; i++) {
       var client = order_client.newClient();
    }
 };

 setInterval(buildClients, INTERVAL_BETWEEN_NEW_CLIENTS_SECS);

 setInterval(function() {
     order_client.log();
 }, INTERVAL_BETWEEN_NEW_CLIENTS_SECS);

 process.on('uncaughtException', function(err) {
   console.log("GLOBAL " + err + "\n" + err.stack);
   console.trace('stack trace');
   //process.exit(1);
 });
 </pre>
<p> The actual client is defined as module, e.g.</p>
<pre class="brush: bash">
 var io = require('socket.io-client');
 var helper = require('helper');
 var MAX_ROWS = process.env.MAX_ROWS || 10;

 var nextClientId = 0;
 var nextRequestId = 0;
 var totalConnected = 0;
 module.exports = function(metrics, hwUsage) {
    var OrderClient = function() {
       this.id = ++nextClientId;
       this.connected = false;
       this.client = io.connect('localhost', { port: 8124,
                               transports: ['websocket'],
                               'force new connection': true,
                               'sync on disconnect' : true,
                               'connect timeout': 500,
                               'reconnect': true,
                               'reconnection delay': 500,
                               'reopen delay': 500,
                               'max reconnection attempts': 5});
    };

    //
    OrderClient.prototype.setupConnectListener = function() {
       var that = this;
       this.client.on('connect_failed', function(error) {
          this.connected = false;
       });

       this.client.on('connect', function() {
          this.connected = true;
          totalConnected++;
       });

       this.client.on('disconnect', function() {
          this.connected = false;
          totalConnected--;
       });

       this.client.on('reconnect_failed', function() {
          this.connected = false;
       });

       this.client.on('end', function() {
          this.connected = false;
       });
    };    

    OrderClient.prototype.sendRequest = function(max) {
       var execs = [];
       var date = new Date();
       for (var i=0; i&lt;max; i++) {
          execs.push({sequence:i, activityDateStr:date.toString(), com: helper.random(5),price: helper.random(100), accountId:1772139, symbol:"QQQ", transaction:"STO", description:"QQQ Stock", qty: helper.random(200), key: "QQQ:::S", netAmount: helper.random(1000)});
       }
       var request = {request: ++nextRequestId, rows:max, timestamp : date.getTime(), address:this.id, execs: execs};
       this.client.emit('order-request', request);
    };
    OrderClient.prototype.setupResponseListener = function() {
       var that = this;
       this.client.on('order-response', function(response) {
          var metric = metrics.update(response.address, response.request, response.timestamp, JSON.stringify(response).length);
        });
    };

    return {
       log: function() {
          hwUsage.stop(function(usage) {
             console.log('clients,connected,' + hwUsage.heading() + ',' + metrics.heading());
             console.log(nextClientId + ',' + totalConnected + ',' + usage.toString() + ',' + metrics.summary().toString());
          });
       },
       newClient: function() {
          var client = new OrderClient();
          client.setupConnectListener();
          client.setupResponseListener();
          setInterval(function() {
             client.sendRequest(helper.random(MAX_ROWS));
          }, helper.random(250) + 50);
          return client;
       }
    };
 }
</pre>
<h3>First blocker</h3>
<p> I started testing on my Linux machine and saw <b>RangeError: Maximum call stack size exceeded</b> after server reached about 2000 connections. It turned out JSON library on socket.io went into recursion when it received messages while it's parsing existing message. I found a patch at <a href="https://github.com/LearnBoost/socket.io/pull/983">Fix infinite recursion in Websocket parsers</a> and manually applied it as I didn't see it on the latest version socket.io library 0.8.9. After applying it, I was able to make some progress.</p>
<h3>Second blocker</h3>
<p> After reaching about 10,000 connections I started seeing <b>Cannot call method 'packet' of null</b> coming from the socket.js. At the time, I was using default configuration for Websockets and apparently it was falling back to xhr-polling under heavy load (See <a href="https://github.com/LearnBoost/socket.io-client/issues/337">open bug</a> for it). I changed configuration to explicitly defined websocket protocol without any fallback, e.g.</p>
<pre class="brush: javascript">
 io.set('transports', ['websocket']);
 </pre>
<p> I also made some changes to network settings on my Linux machine as recommended by <a href="http://blog.caustik.com/2012/08/19/node-js-w1m-concurrent-connections/">Node.js w/1M concurrent connections!</a>.</p>
<pre class="brush: bash">
 net.core.rmem_max = 33554432
 net.core.wmem_max = 33554432
 net.ipv4.tcp_rmem = 4096 16384 33554432
 net.ipv4.tcp_wmem = 4096 16384 33554432
 net.ipv4.tcp_mem = 786432 1048576 26777216
 net.ipv4.tcp_max_tw_buckets = 360000
 net.core.netdev_max_backlog = 2500
 vm.min_free_kbytes = 65536
 vm.swappiness = 0
 net.ipv4.ip_local_port_range = 1024 65535
 </pre>
<p> I then applied above changes using:</p>
<pre class="brush: bash">
 sudo sysctl -w net.core.somaxconn=10000
 sudo sysctl -w net.ipv4.tcp_max_syn_backlog=10000
 sudo sysctl -p
 </pre>
<h3>Third blocker</h3>
<p> Above changes got me to about 17,000 connections but then I started seeing connection timeout on the client side and <b>warn: client not handshaken client should reconnect</b> on the server side. are two open bugs: <a href="https://github.com/LearnBoost/socket.io/issues/438">first</a> and <a href="https://github.com/LearnBoost/socket.io/issues/996">second</a> for it. I added reconnect parameters to client connection, e.g.</p>
<pre class="brush: javascript">
                               'reconnect': true,
                               'reconnection delay': 500,
                               'reopen delay': 500,
                               'max reconnection attempts': 5});
 </pre>
<h3>Fourth blocker</h3>
<p> Under heavy load, I also saw "Error: not opened" coming from WebSocket library, e.g.</p>
<pre class="brush: javascript">
     at WebSocket.send (/alldata/home/sbhatti/prototype-nodejs/node_modules/socket.io-client/node_modules/ws/lib/WebSocket.js:175:16)
     at Transport.WS.send (/alldata/home/sbhatti/prototype-nodejs/node_modules/socket.io-client/lib/transports/websocket.js:107:22)
     at Transport.packet (/alldata/home/sbhatti/prototype-nodejs/node_modules/socket.io-client/lib/transport.js:178:10)
     at Transport.WS.payload (/alldata/home/sbhatti/prototype-nodejs/node_modules/socket.io-client/lib/transports/websocket.js:120:12)
     at Socket.flushBuffer (/alldata/home/sbhatti/prototype-nodejs/node_modules/socket.io-client/lib/socket.js:327:20)
     at Socket.setBuffer (/alldata/home/sbhatti/prototype-nodejs/node_modules/socket.io-client/lib/socket.js:314:14)
     at Socket.onConnect (/alldata/home/sbhatti/prototype-nodejs/node_modules/socket.io-client/lib/socket.js:409:14)
     at Transport.onConnect (/alldata/home/sbhatti/prototype-nodejs/node_modules/socket.io-client/lib/transport.js:139:17)
     at Transport.onPacket (/alldata/home/sbhatti/prototype-nodejs/node_modules/socket.io-client/lib/transport.js:91:12)
     at Transport.onData (/alldata/home/sbhatti/prototype-nodejs/node_modules/socket.io-client/lib/transport.js:69:16)
 </pre>
<p> I chose to ignore it as Websocket is attempting to send data when connection is closed and client will just reconnect.</p>
<h3>Summary</h3>
<p> I am still evaluating Node.js and I have not decided if I would recommend Node.js for streaming solution. I will be comparing these results against some Java solutions such as <a href="https://github.com/Atmosphere/atmosphere">Atomosphere</a> and <a href="http://vertx.io/">Vertx.io</a> and will post those results in future.</p>
<p> <script type="text/javascript">
      SyntaxHighlighter.all()
 </script> </p>
<div style='background-color: white'>
<div id="fb-root">
 </div>
<p> <script>
      window.fbAsyncInit = function() {
           FB.init({appId: '119853371386770', status: true, cookie: true, xfbml: true});
         };
         (function() {
           var e = document.createElement('script'); e.async = true;
           e.src = document.location.protocol +
             '//connect.facebook.net/en_US/all.js';
           document.getElementById('fb-root').appendChild(e);
         }());
 </script></p>
<div id="facebook">
   <fb:comments xid="ScalingNodejs" simple="1" publish_feed="false" width="642" title='a comment on "Scaling Node.js and Websockets"'><br />
   </fb:comments>
 </div>
</p></div>
]]></content:encoded>
			<wfw:commentRss>http://weblog.plexobject.com/?feed=rss2&amp;p=1697</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Tips from Unusually Excellent: The Necessary Nine Skills Required for the Practice of Great Leadership</title>
		<link>http://weblog.plexobject.com/?p=1696</link>
		<comments>http://weblog.plexobject.com/?p=1696#comments</comments>
		<pubDate>Mon, 17 Sep 2012 14:17:08 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Business]]></category>

		<guid isPermaLink="false">http://weblog.plexobject.com/?p=1696</guid>
		<description><![CDATA[I recently read <a href="http://www.amazon.com/Unusually-Excellent-Necessary-Required-Leadership/dp/0470928433">Unusually Excellent: The Necessary Nine Skills Required for the Practice of Great Leadership</a>. Here are a few tips I enjoyed from the book:
]]></description>
			<content:encoded><![CDATA[<p>
 I recently read <a href="http://www.amazon.com/Unusually-Excellent-Necessary-Required-Leadership/dp/0470928433">Unusually Excellent: The Necessary Nine Skills Required for the Practice of Great Leadership</a>. Here are a few tips I enjoyed from the book:</p>
<h2>Credibility</h2>
<h3>Earning the Right to Lead Through Character</h3>
<p> This book shows that in order to gain credibility, you need to be authentic, trustworthy, and have character traits such as courage, integrity, and commitment:</p>
<ul>
<li>Being Authentic
<li>Look at Life: Seeing Who You Are
<li>Owning Your Past: The Sting of Failure &#8211; Adversity demands more of us than normal times do.
<li>One Day at a time &#8211; An Unexpectedly Bad Day
<li>Share the Shame &#8211; You can share a couple of your past disappointments with your team mates to connect with them on a personal level
<li>Face Time &#8211; Meet face to face to build personal relationships
<li>The Perception Gap &#8211; Get feedback on how others see you
<li>The Courage to Listen
<li>Honest Feedback &#8211; great leaders don&#8217;t avoid conflict and give honest feedback but at the same time be authentic and professional.
 </ul>
<h2>Being Trustworthy</h2>
<p> The book shows that great leaders build a track record of honesty, fairness, and integrity that creates a leadership “equity” within their constituency. Trustworthiness takes precedence over heavyweight attributes like creativity and intelligence.</p>
<ul>
<li>Safely Successful &#8211; physical, emotional and professional safety is primarl need.
<li>Be honest &#8211; match your actions with your words and match those words with the truth we see in the world (no spin).
<li>Be vulnerable &#8211; showing your weakness or raw emotion
<li>Be fair
<li>A better place for all &#8211; The book recommends building trusted interpersonal relationships that have commitments to work and loyality. On the other hand fear inspires defensive behavior, which leaders can eliminate fear by being transparent, crystal clear, and integrity.
<li>A Culture of Trust Is a Culture of Truth &#8211; One reason people within enterprises fear telling the truth to each other and to their bosses is that they know the organization cannot properly distinguish between the message and the messenger.
<li>Bad News Doesn&#8217;t Swim Upstream
<li>A Culture of Trust Is a Culture of Innovation &#8211; Trust is the basis of safety. Create trust, and you&#8217;ll create a safe place to take risks and in turn build culture of innovation. The organizations should not punish &#8220;good failure&#8221;
<li>A Culture of Trust Is a Culture of Performance &#8211; you should never punish a good person for delivering bad news—or even, on occasion, bad work.
<li>Take Your Pain Quickly and Acutely—and Move On
</ul>
<h2>Being Compelling &#8211; Commitment to Winning</h2>
<p> The book shows that great leaders evoke the emotion and energy of being involved in a crusade. No one will sacrifice for a project if the leader hasn&#8217;t made a full and clear—and public—commitment. Great leaders don&#8217;t want to be merely an employee instead they want to be part of a team, working together to create something important.</p>
<ul>
<li>Choice and Obligation  &#8211; The best, most talented followers are really volunteers, and because of those very attributes they are often in considerable demand elsewhere.
<li>Attracting the Best and Brightest &#8211; Great leaders engage and listen to people.
<li>Keeping Your Best on Board
<li>Cheerleader
<li>Tell Me the Truth &#8211; the best people actually find reality, even if it is bad news, compelling.
<li>Keep Me Challenged &#8211; Talented people want and need challenging work.
<li>No Hard Feelings &#8211; leaders must be able to stand in their followers&#8217; shoes and see themselves from that viewpoint.
 </ul>
<h2>Competence: Leading on the Field with Skill</h2>
<ul>
<li>Leading People Talent to Teams &#8211; Hiring great people is arguably the highest-leverage activity that leaders undertake.
<li>Seating Chart &#8211;  talent is useless if the person is not a good match with that role and responsibility and a specific place in the structure of the organization.
<li>People First &#8211; hire the very best people; only then should you focus on building the right plan for the organization.
<li>Engagement &#8211; engage people by setting realistic goals with them and fairly rewarding them for meeting or exceeding those expectations.
<li>Enrollment
<li>Expectations
<li>Energy &#8211; functional, emotional and career energy.
<li>Empowerment &#8211; delegate power to other people
<li>Retreat to Attack
<li>How Has the Nature of Your Enterprise Changed? &#8211; As a leader, if you have not prepared your people for that change or you resist that change, you have failed in your responsibilities.
<li>Where Is Your Authority or Positional Power Best Used in Leading People? &#8211; By carefully setting performance expectations with your key team members, you move the whole game up a notch.
<li>What Is Your Plan to Deal with Your Weakest Link?  &#8211; being aware of poorly performing subordinate and acting on it instead of avoiding it.
<li>Will You Distinguish the Bad Performer from a Bad Plan? &#8211;  think like a venture capitalist, with your project leaders as the entrepreneurs and the project itself a new venture. Have a post-mortem and inquire why project failed.
 </ul>
<h2>Leading Strategy &#8211; ideas to plans</h2>
<p> Leaders need to distinct between leading people, strategy and execution.</p>
<ul>
<li> Process to Plans &#8211; plan shows what needs to be done, where as trategy is bigger than plan and includes how things are done and fallback options.
<li>The Process: Inclusive and Collaborative &#8211; The process must include the best people and the best ideas, from both within and outside the company, and must foster collaborative thinking and constructive, rigorous discussion.
<li>Winnowing Out a Plan &#8211; solicit ideas from others when you don&#8217;t know the domain
<li>The Plan: Realistic and Compelling &#8211; The book shows that leaders need to be engaged throughout the process to make sure the process moves along with appropriate energy and that the team remains realistic in terms of time, resources, and goals.
<li>Stickiness &#8211; commitment in the face of adversity
</ul>
<h2>Leading Execution &#8211; actions to results </h2>
<p> Execution is about results.  Leaders need to distinct between leading people, strategy and execution. Execution provides feedback that can be measured against plans.</p>
<ul>
<li>Solve the Hard Problems First &#8211; don&#8217;t distract yourself with second-tier tasks
<li>At the Edges &#8211; In order to build high-reliability organizations (e.g. SWAT), you need zero tolerance team execution, which require:
<ul>
<li>reliable communications
<li>continuous training
<li>standardize and synchronize
<li>mission-goal clarity and loyality
<li>empower the front line
<li>redundancy
    </ul>
<li>Leadership Leverage in Execution &#8211; The book suggests leading the process and setting the standards for the right goals. This includes leading the design process to create the appropriate metrics, ensuring a winner&#8217;s commitment and making sure that attitude permeates the culture.
<li>Curb Your Enthusiasm: Focus, Commit, and Deliver &#8211; don&#8217;t overcommit and follow the rule of “first things first.”
<li>It Isn&#8217;t Real If You Don&#8217;t Measure It &#8211; Measuring what matters is an extremely high-leverage opportunity. Use management by objectives (MBO), “as measured by” (AMB) or a key performance indicator (KPI) processes for measureing factors that correlate very highly with winning.
<li>Let the Dashboard Drive &#8211; Measuring what matters to naturally direct attention, focus, and commitment to the right activities
<li>It&#8217;s Just Like Pinball: If You Win, You Get to Play Again
<ul>
<li>Winner&#8217;s mindset
<li>Failing elgantly &#8211; No lame excuses
    </ul>
<li>Sloppiness &#8211; HRO never allow sloppiness because they know it equals death. The book shows that leaders may<br />
 feel like part of being a nice guy, succumbing to that temptation promotes a culture of mediocrity.</p>
<li>Performance Feedback &#8211; look for data coming back from the field.
</ul>
<h2>Consequence: Creating a Culture, Leaving a Legacy of Values</h2>
<p> Trust is the most fragile of assets; at a certain point, different in every situation. </p>
<p>
 <center><b>Legacy = Culture + Reputation</b></center></p>
<h2>A Leader&#8217;s Communication </h2>
<ul>
<li>Open, Honest Dialogue &#8211; The book shows that the ability of leaders to communicate effectively is highest leverage activity in their set of responsibilities and should include:
<ul>
<li>What are we doing? (Vision and mission.)
<li>Why are we doing it? (Purpose and goals.)
<li>What&#8217;s the plan to win?
<li>(What&#8217;s the strategy here?)
<li>How are we doing? (Results and status—health of the business.)
<li>What is my part in the game? (What do you expect from me?)
<li>What&#8217;s in it for me? (Why is this a compelling place for me to be?)
<li>How am I doing? (Give me feedback, acknowledgment, appreciation.)
    </ul>
</ul>
<h3>Talking Trust</h3>
<p> In order to build trust, leaders not only need to focus on contents but also emotional content of that message and the<br />
 connection—the leader&#8217;s empathy with the audience.</p>
<h3>Checklists and Guideposts </h3>
<p> Here are some key points from the book:</p>
<ul>
<li>communication is a core responsibility of leading
<li>most of the important things in organizations are the result of the right conversation
<li>starving followers from basic information will result in high cost
 </ul>
<p> Here are five C&#8217;s for What question leader needs to communicate:</p>
<ul>
<li>A compelling cause
<li>Credibility &#038; Competence
<li>Character
<li>Commitment
<li>Contribution
 </ul>
<p> Here are five E&#8217;s for Why question leader needs to communicate:</p>
<ul>
<li>Engagement
<li>Enrollment
<li>Energy
<li>Empowerment
<li>Endorsement
 </ul>
<p> Here are six C&#8217;s for When question leader needs to communicate:</p>
<ul>
<li>Context
<li>Confidence
<li>Challenge
<li>Collaboration
<li>Culture
<li>Coaching
 </ul>
<p> Here are seven C&#8217;s for How question leader needs to communicate:</p>
<ul>
<li>Clarity
<li>Consistency
<li>Carefulness
<li>Courage
<li>Conviction
<li>Compassion
<li>Completion
 </ul>
<h3>The Solitary Touch </h3>
<p> The book shows that there is really no such thing as a “casual” conversation.</p>
<h3>Your 24 × 7 Job</h3>
<p> The book shows leader has three basic tasks:</p>
<ul>
<li>Align the interests, energy, and commitment of the team.
<li>Reduce fear, confusion, and anxiety.
<li>Instill confidence and trust, while rallying support and contributions.
 </ul>
<h2>A Leader&#8217;s Decision Making Values-Based Choices</h2>
<p> The book shows that leader does not need to make most of the decisions, but need to help followers make make better decisions.</p>
<h3>Decision Structure</h3>
<ul>
<li>What Exactly Are We Deciding?
<li>What Flavor Is This Decision? &#8211; decisions can be classified as either simple or complex. You need sufficient data to make the decision, otherwise you have to use intuition. Decisions can also be characterized as easy or difficult.
<li>When Does This Decision Need to Be Made?<br />
 <br />
 <b><center>Total Cycle Time = Time to Decide + Time to Commit + Time to Execute</center></b></p>
<li>Who Should Make This Decision?  &#8211; who is best equipped—by skill, experience, proximity
<li>Don&#8217;t Wait; Decide
<li>Chasing Decisions &#8211; communicate with followers and empower them to make decisions
 </ul>
<h2>A Leader&#8217;s Impact The Transfer of Influence from Leader to Follower</h2>
<p> Finally, the book shows how to build lasting legacy and reputation:</p>
<ul>
<li>Leader Taking the High Ground
<li>Whisper Campaign &#8211; use public forums to acknowledge accomplishments, sacrifices and courage. Also, appreciate them in private.
<li>All You Leave Behind &#8211; using exit interviews to get feedback
<li>Collective Memory
<li>What to Do
<li>Pay attention to change
<li>Get More Curious, and Smarter, About Human
<li>Nature &#8211; leaders tend to gravitate toward the objective and away from the subjective.
<li>Give feedback
<li>Celebrate success
<li>Respect Life Outside of Work
<li>Your Greatest Legacy
 </ul>
<div style='background-color: white'>
<div id="fb-root">
 </div>
<p> <script>
      window.fbAsyncInit = function() {
           FB.init({appId: '119853371386770', status: true, cookie: true, xfbml: true});
         };
         (function() {
           var e = document.createElement('script'); e.async = true;
           e.src = document.location.protocol +
             '//connect.facebook.net/en_US/all.js';
           document.getElementById('fb-root').appendChild(e);
         }());
 </script></p>
<div id="facebook">
   <fb:comments xid="leadership" simple="1" publish_feed="false" width="642" title="Unusually Excellent: The Necessary Nine Skills Required for the Practice of Great Leadership"><br />
   </fb:comments>
 </div>
</p></div>
]]></content:encoded>
			<wfw:commentRss>http://weblog.plexobject.com/?feed=rss2&amp;p=1696</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Building a streaming quote server using Node.js and Websockets</title>
		<link>http://weblog.plexobject.com/?p=1695</link>
		<comments>http://weblog.plexobject.com/?p=1695#comments</comments>
		<pubDate>Thu, 06 Sep 2012 17:32:44 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Javascript]]></category>

		<guid isPermaLink="false">http://weblog.plexobject.com/?p=1695</guid>
		<description><![CDATA[<h2>Using Node.js and Websockets to implement streaming quote server</h2>
 A couple of years ago, I implemented a quote server using XMPP, Bosh, Strophe and Ejabbberd at work and described an <a href="http://weblog.plexobject.com/?p=1676">overview of the ]]></description>
			<content:encoded><![CDATA[<p>A couple of years ago, I implemented a quote server using XMPP, Bosh, Strophe and Ejabbberd at work and described an <a href="http://weblog.plexobject.com/?p=1676">overview of the design and code in my blog</a>. We have been looking at Node.js and Websockets recently for streaming data so I will describe a simple implementation of quote server here using those two technologies:</p>
<h3>Quote Server</h3>
<p> The quote server uses Web sockets and listens for subscribe-stock-quote message to subscribe for a particular quote and unsubscribe-stock-quote message to unsubscribe.<br />
 Here is a simple implementation of the Quote server:</p>
<pre class="brush: javascript">
 var express = require('express')
    , sio = require('socket.io')
    , http = require('http')
    , quoteSubscriptions = require('quote_subscriptions')
    , app = express();

 var server = http.createServer(app);
 var sequence = 0;
 app.configure(function () {
    app.use(express.static(__dirname + '/public'));
    app.use(app.router);
 })

 var io = sio.listen(server);
 io.set('log level', 1);
 server.listen(8080);

 io.sockets.on('connection', function(socket) {
    // subscribe to stock quotes
    socket.on('subscribe-stock-quote', function(symbol) {
       console.log('subscribe-stock-quote received');
       quoteSubscriptions.subscribe(socket, symbol, function() {
          socket.emit('subscribed-stock-quote', 'You have been subscribed to ' + symbol);
       });
    });

    // unsubscribe to stock quotes
    socket.on('unsubscribe-stock-quote', function (symbol) {
       console.log('unsubscribe-stock-quote received');
       quoteSubscriptions.unsubscribe(socket, symbol, function() {
          socket.emit('unsubscribed-stock-quote received', 'You have been unsubscribed to ' + symbol);
       });
    });

    // disconnected
    socket.on('disconnect', function() {
       quoteSubscriptions.unsubscribeAll(socket, function() {
          socket.emit('disconnected', socket.symbol + ' has been unsubscribed from stock quotes.');
       });
    });
 });

 // update clients every 500 milli-seconds with latest stock quotes
 setInterval(function () {
    quoteSubscriptions.pushUpdates();
    io.sockets.emit('sequence', ++sequence);
 }, 500);
</pre>
<p> In addition to sending quote updates, the server also sends a loop counter so that we know how many times quotes have been pushed to clients and that message is broadcasted to all clients.</p>
<p>
 The subscriptions are managed in another module quote_subscriptions, i.e.,:</p>
<pre class="brush: javascript">
 ////////////////////////////////////////////////////////////////////////////////////////////
 //
 // This module maintains quote subscriptions for all clients
 //
 ////////////////////////////////////////////////////////////////////////////////////////////
 var yclient = require('y_client');
 var stocksSubscribers = {};
 var quoteCache = {};
 exports.subscribe = function(socket, symbol, callback) {
    if (typeof socket.stockSymbols === "undefined") {
       socket.stockSymbols = [];
    }
    if (socket.stockSymbols.indexOf(symbol) == -1) {
       socket.stockSymbols.push(symbol);
    }
    //
    var subscribers = stocksSubscribers[symbol];
    if (typeof subscribers === "undefined") {
       subscribers = [];
       stocksSubscribers[symbol] = subscribers;
    }
    if (subscribers.indexOf(socket) == -1) {
       subscribers.push(socket);
    }
    callback();
 }

 exports.unsubscribeAll = function(socket, callback) {
    if (typeof socket.stockSymbols === "undefined") {
       return;
    }
    for (var i=0; i&lt;socket.stockSymbols.length; i++) {
       this.unsubscribe(socket, socket.stockSymbols[i], function() {});
    }
    callback();
 }

 exports.unsubscribe = function(socket, symbol, callback) {
    if (typeof socket.stockSymbols === "undefined") {
       return;
    }
    var ndx = socket.stockSymbols.indexOf(symbol);
    if (ndx !== -1) {
       socket.stockSymbols.splice(ndx, 1);
    }
    //
    var subscribers = stocksSubscribers[symbol];
    if (typeof subscribers === "undefined") {
       return;
    }
    ndx = subscribers.indexOf(socket);
    if (ndx !== -1) {
       subscribers.splice(ndx, 1);
    }
    callback();
 }
 exports.pushUpdates = function() {
    for (var symbol in stocksSubscribers) {
       var sockets = stocksSubscribers[symbol];
       if (sockets.length == 0) {
          continue;
       }
       quote = quoteCache[symbol];
       if (typeof quote === "undefined") {
          yclient.quote(symbol, function(quote) {
             quoteCache[symbol] = quote;
             sendQuote(quote, sockets);
          });
       } else {
          sendQuote(quote, sockets);
       }
    }
 }

 function sendQuote(quote, sockets) {
    var rnd = Math.random();
    quote.counter = quote.counter + 1;
    quote.bid = round(rnd % 2 == 0 ? quote.original_bid + rnd :  quote.original_bid - rnd, 2);
    quote.ask = round(rnd % 2 == 0 ? quote.original_ask + rnd :  quote.original_ask - rnd, 2);
    for (var i=0; i&lt;sockets.length; i++) {
       var socket = sockets[i];
       var address = socket.handshake.address;
       console.log('Quote sending ' + quote.symbol + ' ' + quote.counter + ' to ' + address.address + ":" + address.port);
       socket.volatile.emit('receive-stock-quote', quote);
    }
 }

 function round(num, dec) {
    return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
 }
</pre>
<p> The above module defines methods to subscribe, unsubscribe and push quote updates to all clients. Note that above module requests quotes from Yahoo if it doesn&#8217;t exist in the cache and then randomly changes bid/ask values.</p>
<h3>Yahoo Finance Client</h3>
<p> I used Yahoo finance to retrieve the quotes, however these quotes are cached and modified by the quote subscription module:</p>
<pre class="brush: javascript">
 ///////////////////////////////////////////////////////////////////////////////////////////
 //
 // This module provides quote/chain apis using yahoo finance
 //
 ////////////////////////////////////////////////////////////////////////////////////////////
 var http = require('http')
   , libxmljs = require('libxmljs')
   , util = require('util')

 exports.quote = function(symbol, callback) {
    api('download.finance.yahoo.com', '/d/quotes.csv?s=' + symbol + '&#038;f=nb2b3ra2yj3e7gopjkl1xm3m4h', function(text) {
       cols = text.split(',');
       callback({'symbol':cols[0], 'ask':cols[1], 'bid':cols[2], 'counter':0, 'original_ask':cols[1], 'original_bid':cols[2]});
    });
 }  

 function api(host, path, callback) {
    var options = {
        host: host,
        port: 80,
        path: path,
        method: 'GET',
        headers: {'User-Agent': 'Node'}
    };
    var req = http.request(options, function(res) {
      res.setEncoding('utf8');
      var text = '';
      res.on('data', function (chunk) {
        text += chunk;
      });
      res.on('end', function (e) {
         text = text.replace(/"/g, '');
         callback(text);
      });
      res.on('error', function (e) {
        console.log('Could not send api with response : ' + e.message);
      });
    });

    req.on('error', function(e) {
      console.log('Could not send api with response : ' + e.message);
    });

    // write data to request body
    req.end();
 }
</pre>
<h3>Web Client</h3>
<p> Here is an example of Web client that allows users to subscribe to different stock symbols and receive streaming quotes every 500 milliseconds.</p>
<pre class="brush: xml">
 &lt;!doctype html&gt;
 &lt;html lang="en"&gt;
    &lt;head&gt;
       &lt;meta charset="utf-8"&gt;
       &lt;title&gt;OptionsHouse Streaming&lt;/title&gt;
       &lt;script src="/socket.io/socket.io.js"&gt;&lt;/script&gt;
       &lt;script&gt;
          var socket = io.connect('ws://localhost:8080');
          var lastSymbol;
          var lastQuote;
          socket.on('connect', function() {
             lastSymbol = 'AAPL';
             socket.emit('subscribe-stock-quote', lastSymbol);
          });         socket.on('disconnect', function() {
             alert('remote server died');
          });
          socket.on('sequence',function(seq) {
             var seqDiv = document.getElementById('sequence');
             seqDiv.innerHTML = seq;
          });
          socket.on('receive-stock-quote',function(quote) {
             var company = document.getElementById('company');
             var counter = document.getElementById('counter');
             var bid = document.getElementById('bid');
             var ask = document.getElementById('ask');
             var bidColor = '&lt;font color="black"&gt;'
             var askColor = '&lt;font color="black"&gt;'
             if (typeof lastQuote !== "undefined") {
                if (quote.bid &gt; lastQuote.bid) {
                   bidColor = '&lt;font color="green"&gt;'
                } else if (quote.bid &lt; lastQuote.bid) {
                   bidColor = '&lt;font color="red"&gt;'
                }
                if (quote.ask &gt; lastQuote.ask) {
                   askColor = '&lt;font color="green"&gt;'
                } else if (quote.ask &lt; lastQuote.ask) {
                   askColor = '&lt;font color="red"&gt;'
                }
             }
             company.innerHTML = quote.symbol;
             counter.innerHTML = quote.counter;
             bid.innerHTML = bidColor + quote.bid + '&lt;/font&gt;';
             ask.innerHTML = askColor + quote.ask + '&lt;/font&gt;';
             lastQuote = quote;
          });

          window.addEventListener('load',function() {
             document.getElementById('start').addEventListener('click',
             function() {
                if (typeof lastSymbol !== "undefined") {
                   socket.emit('unsubscribe-stock-quote', lastSymbol);
                }
                var symbol = document.getElementById('symbol').value;
                socket.emit('subscribe-stock-quote', symbol);
                lastSymbol = symbol;
             }, false);
             document.getElementById('stop').addEventListener('click',
             function() {
                var symbol = document.getElementById('symbol').value;
                socket.emit('unsubscribe-stock-quote', symbol);
             }, false);
          }, false);
       &lt;/script&gt;
    &lt;/head&gt;
    &lt;body&gt;
       &lt;div id="form"&gt;
          Symbol: &lt;input type="text" id="symbol" size="10" value="AAPL" /&gt;
          &lt;input type="button" id="start" value="Subscribe" /&gt;
          &lt;input type="button" id="stop" value="Unsubscribe" /&gt;
       &lt;/div&gt;
       &lt;table width="500"&gt;
          &lt;tr&gt;
             &lt;th&gt;Company&lt;/th&gt;
             &lt;th&gt;Bid&lt;/th&gt;
             &lt;th&gt;Ask&lt;/th&gt;
             &lt;th&gt;Stock Counter&lt;/th&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
             &lt;td align="center"&gt; &lt;div id="company"&gt;&lt;/div&gt; &lt;/td&gt;
             &lt;td align="center"&gt; &lt;div id="bid"&gt;&lt;/div&gt; &lt;/td&gt;
             &lt;td align="center"&gt; &lt;div id="ask"&gt;&lt;/div&gt; &lt;/td&gt;
             &lt;td align="center"&gt; &lt;div id="counter"&gt;&lt;/div&gt; &lt;/td&gt;
          &lt;/tr&gt;
       &lt;/table&gt;
       &lt;p&gt;&lt;/p&gt;
       Quote Server Loop: &lt;span id="sequence"/&gt;
    &lt;/body&gt;
 &lt;/html&gt;
</pre>
<h3>Running the example</h3>
<p> You can download all the code from <a href="https://github.com/bhatti/node-quote-server">My github</a> account, e.g.</p>
<pre class="brush: shell">
 git clone https://github.com/bhatti/node-quote-server.git
 cd node-quote-server
 npm install
 node app
 </pre>
<p> Then you can point your browser to http://localhost:8080 and start playing:</p>
<p>
 <img src="/images/streaming.png"></p>
<p><h3>Summary</h3>
<p> Node.js comes with good support for Websockets so it takes only few lines to build the quote server. I am still testing Node.js and Websockets with different browsers and simulating load with large number of clients and will post those results in future. </p>
<p> <script type="text/javascript">
      SyntaxHighlighter.all()
 </script> </p>
<div style='background-color: white'>
<div id="fb-root">
 </div>
<p> <script>
      window.fbAsyncInit = function() {
           FB.init({appId: '119853371386770', status: true, cookie: true, xfbml: true});
         };
         (function() {
           var e = document.createElement('script'); e.async = true;
           e.src = document.location.protocol +
             '//connect.facebook.net/en_US/all.js';
           document.getElementById('fb-root').appendChild(e);
         }());
 </script></p>
<div id="facebook">
   <fb:comments xid="QuoteNodejs" simple="1" publish_feed="false" width="642" title='a comment on "Building a streaming quote server using Node.js and Websockets"'><br />
   </fb:comments>
 </div>
</p></div>
]]></content:encoded>
			<wfw:commentRss>http://weblog.plexobject.com/?feed=rss2&amp;p=1695</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Back from OSCON 2012</title>
		<link>http://weblog.plexobject.com/?p=1694</link>
		<comments>http://weblog.plexobject.com/?p=1694#comments</comments>
		<pubDate>Wed, 08 Aug 2012 17:58:45 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Computing]]></category>

		<guid isPermaLink="false">http://weblog.plexobject.com/?p=1694</guid>
		<description><![CDATA[I went back to OSCON last month (July 2012), which was held in Portland again. I saw the biggest crowd this year and over 3000 folks attended the conference. Here are some of the tutorials and sessions I attended:
 
 <h3>R</h3>I attended <a href="http://c]]></description>
			<content:encoded><![CDATA[<p>I went back to OSCON last month (July 2012), which was held in Portland again. I saw the biggest crowd this year and over 3000 folks attended the conference. Here are some of the tutorials and sessions I attended:</p>
<h3>R</h3>
<p>I attended <a href="http://courses.had.co.nz/12-oscon/">R tutorial</a> on the first day, which I found quite informative. There has been a lot of interest in Data Science and R is a great tool for statistical analysis and graphs.</p>
<h3>Scala</h3>
<p>On the second half of Monday, I attended <a href="http://www.oscon.com/oscon2012/public/schedule/detail/24350">Scala Koans</a>, which was disappointing. First, they could not get us started with the session as Wifi died on us and they didn&#8217;t have much backup plan. We finally started the session after waiting for an hour and then were mostly left on our own to finish the Koans. Yeah, I could have done that myself by downloading <a href="https://bitbucket.org/dmarsh/scalakoansexercises">Koans</a>.</p>
<h3>Android-Fu</h3>
<p>On Tuesday, I attended <a href="http://www.oscon.com/oscon2012/public/schedule/detail/24288">Android-Fu</a>, which was somewhat useful. I have had quite a bit Android development at work, but I got a few pointers.</p>
<h3>Android Testing</h3>
<p>The second half of Tuesday, I attended <a href="http://www.oscon.com/oscon2012/public/schedule/detail/23774">Android Testing</a>, which was useful but the presenter was incredible boring and had hard time keeping awake. <a href="http://cdn.oreillystatic.com/en/assets/1/event/80/Introduction%20to%20Android%20Testing%20Presentation.pdf">[Slides]</a></p>
<h3>Go</h3>
<p>The real conference started on Wednesday and I attended Go session, which was mostly about governance behind Go language.</p>
<h3>Storm</h3>
<p> I then attended session on <a href="http://www.oscon.com/oscon2012/public/schedule/detail/24054">Storm</a>, which was informative described differences between Hadoop and Storm and showed some actual code.</p>
<h3>MongoDB</h3>
<p>I then attended session on <a href="http://www.oscon.com/oscon2012/public/schedule/detail/25980">Running MongoDB for High Availability</a>, which showed some of weak areas and lessons learned while scaling MongoDB. <a href="http://cdn.oreillystatic.com/en/assets/1/event/80/Running%20MongoDB%20for%20High%20Availability%20Presentation.bin"> [Slides]</a></p>
<h3>Apache Zookeeper</h3>
<p> This <a href="http://www.oscon.com/oscon2012/public/schedule/detail/24190">session</a> was also very informative and it showed various uses of Zookeeper.</p>
<h3>Disruptor</h3>
<p>I then attended session on <a href="http://www.oscon.com/oscon2012/public/schedule/detail/24441">Disruptor</a>, which is incredibly fast concurrency framework. <a href="http://cdn.oreillystatic.com/en/assets/1/event/80/Concurrent%20Programming%20Using%20The%20Disruptor%20Presentation.pdf">[Slides]</a></p>
<h3>Building Functional Hybrid Apps For The iPhone And Android</h3>
<p> I then attended <a href="http://www.oscon.com/oscon2012/public/schedule/detail/26482">Building Functional Hybrid Apps For The iPhone And Android</a>, which was mostly marketing talk for Websphere IDE and speakers ignored value of PhoneGap (Apache Cordova), which was behind their demo.</p>
<h3>Node.js</h3>
<p> On thursday, I attended session on <a href="http://www.oscon.com/oscon2012/public/schedule/detail/24227">Node.js</a>, which was nice introduction with some code samples. <a href="http://cdn.oreillystatic.com/en/assets/1/event/80/Essential%20Node_js%20for%20Web%20Developers%20Presentation.pdf">[Slides]</a></p>
<p>
 I attended another session on Node.js about <a href="http://www.oscon.com/oscon2012/public/schedule/detail/23864">Node.js in Production: Postmortem Debugging and Performance Analysis</a>, which showed a number of ways to debug your Node.js applications. <a href="http://cdn.oreillystatic.com/en/assets/1/event/80/Node_js%20in%20Production_%20Postmortem%20Debugging%20and%20Performance%20Analysis%20Presentation.pdf">[Slides]</a></p>
<h3>Advanced MySQL Replication Architectures</h3>
<p>This was also very informative <a href="http://www.oscon.com/oscon2012/public/schedule/detail/23994">session</a> and you can take a look at <a href="http://cdn.oreillystatic.com/en/assets/1/event/80/Advanced%20MySQL%20Replication%20Architectures%20%20Presentation.pdf">slides</a>.</p>
<h3>The Art of Organizational Manipulation</h3>
<p>This was entertaining talk about how to build influence in workplace.</p>
<h3>High Performance Network Programming on the JVM</h3>
<p>This was another informative talk about building network applications in Java and you would <a href="http://www.slideshare.net/eonnen/high-performance-network-programming-on-the-jvm-oscon-2012">slides</a> very helpful.</p>
<h3>Summary</h3>
<p> I found very few sessions on mobile this year and there were a couple of sessions on Android and I wanted to see more. There were a lot of vendor sponsored sessions on private clouds and a lot of vendors in exhibition hall were promoting frameworks such as OpenStack, CloudStack, Eucalyptus and others. There were also quite a few sessions on distributed frameworks such as Hadoop, HBase, MongoDB, Zookeeper, Storm, Disruptor, etc, which I enjoyed. I didn&#8217;t see a lot of sessions on functional languages as past and wanted to see some sessions on Clojure (which was cancelled).<br />
 NoSQL Databases – A few sessions were on HBase, MongoDB and Cassandra. There was a lot of enthusiasm for HTML5 again and a lot of sessions were sold out. Having attended similar sessions in past, I skipped most of them. Overall, I had good time but not sure if I would be back next year. You can read presentation slides from <a href="http://www.oscon.com/oscon2012/public/schedule/proceedings">http://www.oscon.com/oscon2012/public/schedule/proceedings</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://weblog.plexobject.com/?feed=rss2&amp;p=1694</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Review of &#8216;Good Boss, Bad Boss: How to Be the Best&#8230; and Learn from the Worst&#8217;</title>
		<link>http://weblog.plexobject.com/?p=1693</link>
		<comments>http://weblog.plexobject.com/?p=1693#comments</comments>
		<pubDate>Tue, 22 May 2012 16:27:09 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Business]]></category>

		<guid isPermaLink="false">http://weblog.plexobject.com/?p=1693</guid>
		<description><![CDATA[I recently finished Bob Sutton's book <a href="http://www.amazon.com/Good-Boss-Bad-Best-Learn/dp/0446556084">Good Boss, Bad Boss</a>, who is well known for his book <a href="http://www.amazon.com/The-Asshole-Rule-Civilized-Workplace/dp/0446698202/ref=pd_s]]></description>
			<content:encoded><![CDATA[<p>I recently finished Bob Sutton&#8217;s book <a href="http://www.amazon.com/Good-Boss-Bad-Best-Learn/dp/0446556084">Good Boss, Bad Boss</a>, who is well known for his book <a href="http://www.amazon.com/The-Asshole-Rule-Civilized-Workplace/dp/0446698202/ref=pd_sim_b_1">The No Asshole Rule: Building a Civilized Workplace and Surviving One That Isn&#8217;t </a>. As most of us, I have many bosses and also manage other people so I have found this book quite useful. Good bosses not only help productivity and work environment but they also reduce stress, diseases or family troubles. </p>
<p> Bob shows that good boses apply Lasorda’s law and use less management, however they don’t ignore their people and help them out. Also, good bosses have mentality of running marathon rather than sprint and they instill grit in followers, where they push them to try a bit harder and be more creative. Bob suggests using small wins and manageable tasks to drive focus and sense of accompllishment in followers. Bob warns against bosses with attitude of toxic tandems, who are self absorbed. Good bosses also back their followers and balance performance and humanity by helping people to do great work and experience pride and dignity.</p>
<p> Here are highlights from Bob&#8217;s book:</p>
<h3>Take Control</h3>
<p> The media generally portrays leaders as heros, but research shows that most bosses have little impact on overall performance of a company. Good bosses use this illusion to their advantage to bring confidence in their followers and increasing odds of their success.</p>
<h3>Don&#8217;t Dither</h3>
<p> Good bosses use crisp language and decide unequivocally, however they are not afraid to change their decisions. They follow the rule of strong opinions that are weakly held.</p>
<h3>Get/Give Credit</h3>
<p> Bosses get credit no matter what but good bosses also give credit to others. I have worked in environments, where bosses took all the glory and passed shit to their followers. However, everyone wins if boss give credits as much as possible.</p>
<h3>Blame yourself</h3>
<p> Good bosses also take the heat for team, which builds loyalty of their followers.</p>
<h3>Strive to be Wise</h3>
<p> Good bosses create balance of over confidence and healthy dose of self doubt. They ask questions and listen instead of talking too much. Wise bosses assume best from their people and show them compassion and love.</p>
<h3>Forgive and Remember</h3>
<p> Wise bosses forgive and remember the mistakes so that they can learn from them instead of blaming followers or forgetting them altogether.</p>
<h3>Safety &#038; Creativity</h3>
<p> Wise bosses create safe environment to share ideas and be more creative. They fight for what they believe in but gracefully accept defeat.</p>
<h3>Participation</h3>
<p> Wise bosses ask good questions, listen and ask for help. They show empathy, compassion and gratitude to their followers. They know their flaws and work with other people to compensate for their weaknesses.</p>
<h3>Stars &#038; Rotten Apple</h3>
<p> Some organizations glorify solo stars, which undermines team collaboration. Good bosses recruit energizers and eliminate bad apples or energy suckers, who undermine constructive actions.</p>
<h3>Keep teams together</h3>
<p> Good bosses keep teams together. I have found that a new team takes a couple of months to gel, and having worked in project-based teams (which was awful) I take this advice to the heart.</p>
<h3>Link Talk &#038; Action</h3>
<p> Good bosses say same simple things and build harmony between their actions and words. They empathizes with their customers by eating their own dog food. They try to reduce complexity and use simple principles, strategies, and metrics. </p>
<h3>Don&#8217;t shirk Dirty Work</h3>
<p> Good bosses confront problems directly, which may include personnel problems such as firing low performer or bad apple. They create realistic expectations for followers and make tough decisions, however they make those decisions with understanding, control and compassion.</p>
<h3>Squelch your inner Bosshole</h3>
<p> Let&#8217;s face it, there are plenty bosses who act like assholes. Unfortunately, most of them are unaware of their attitude and habits. Bossholes create negative work environment and cause health problems for their followers. I have worked in companies, where this was cultural issue and the role models of bossholes was passed from top-down. Nevertheless, this is often the cause of employees leaving companies or having heart attacks. Bob suggests a couple of solutions such as tape method to help manage anger.</p>
<h3>Summary</h3>
<p> As we spend most part of day at work, it helps if the work environment and the boss is empathetic. Bob provides a lot of advice to bosses so that they can build better work environment for their followers.</p>
]]></content:encoded>
			<wfw:commentRss>http://weblog.plexobject.com/?feed=rss2&amp;p=1693</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to survive in today&#8217;s work environments and businesses</title>
		<link>http://weblog.plexobject.com/?p=1692</link>
		<comments>http://weblog.plexobject.com/?p=1692#comments</comments>
		<pubDate>Thu, 26 Apr 2012 22:39:24 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Business]]></category>

		<guid isPermaLink="false">http://weblog.plexobject.com/?p=1692</guid>
		<description><![CDATA[In modern work environments and businesses, it is crucial to expedite learning cycle and create knowledge workplace. In order to shorten the learning cycle, you can apply the Scientific approach, which comprises of three 
 stages, i.e., making a hypothesi]]></description>
			<content:encoded><![CDATA[<p>In modern work environments and businesses, it is crucial to expedite learning cycle and create a knowledge workplace. In order to shorten the learning cycle, you can apply the scientific approach, which comprises of three stages, i.e., making a hypothesis, testing the hypothesis and validating test results. Generally, the test results leads to another hypothesis, and another cycle of experiment, validation and then publishing results.  At the end of each cycle, you learn something knew, thus the shorter your cycle the faster you learn. Following are few variations of this approach used in manufacturing, research and development: </p>
<h3>OODA</h3>
<p> John Boyd revolutionized military aviation strategy by designing lightweight fighter planes that were based on a shorter loop of </p>
<ul>
<li>Observe
<li>Orient
<li>Decide
<li>Act
 </ul>
<p> John showed that light-weight planes that provided quick feedback to pilots proved better in dogfights. It showed that speed of loop beats quality of iteration. </p>
<h3>Theory of Constraint</h3>
<p> The theyory of constraint creates flow of an activity and finds all constraints from end to end and then tries to remove biggest constraint. It then repeats the process and identifies next biggest constraint and solves that constraint. </p>
<h3>Lean Manufacturing</h3>
<p> Lean manufacturing is based on Toyota production system that emphasizes waste elimination and creating value for customers. Lean production system stresses reducing activity time from start to finish and continuous improvement or Kaizen. </p>
<h3>Lean Software Development</h3>
<p> Lean software development applies lessons of Toyota production systems to software development by eliminating waste, reducing in-progress work (inventory), and amplifying learning. It speeds up learning process by short cycle of iterative development. </p>
<h3>Lean Startup</h3>
<p> The startups generally start with visions or hypothesis that are unproven. Lean startups borrow ideas from Toyota production systems by creating rapid prototypes that test market assumptions and uses customer feedback evolve the product. Just like the scientific approach, it uses a cycle of product-idea or hypothesis, product-development or preparing an experiment, releasing the product, where startups collects data about value to users. The test results help startups adjust the product and another round of tests are followed. The Lean Startups emphasized validated learning that you gain from running actual experiment than just guess work. </p>
<h3>Spiral Methodology</h3>
<p> Spiral methodology is a software development process that uses iterative development and encourages prototyping and experimenting. Each iteration starts with objectives, constraints, and alternatives, which are then evaluated, developed and then validated. </p>
<h3>Scrum Methodology</h3>
<p> Scrum is an iterative and incremental agile software methodology for project management.  It uses short sprints for software development, where each sprint starts with planning meeting that defines the user stories or features to be delivered, followed by development and release. At the end of sprint, the team holds Sprint review meeting and retrospective to identify impediments so that team can improve the process. The whole process is repeated with another round of short sprints.</p>
<h3>Other Agile Methodologies</h3>
<p> There are a number of other agile methodologies that also founded on short iterations and incremental development such as Agile Unified Process, Crystal Clear, XP, Feature driven development. These methodologies encourage transparent, collaborative and open work environments, which provide foundation for adoptive and knowledge workplaces.</p>
<h3>Test-Driven Development</h3>
<p> Test-driven development is a software development process uses a short cycle of development, where developer writes a failing test case for desired functionality, then implements functionality to pass the test and finally refactors the new code. It eliminates the waste by focusing on business functionality that is required and helps build design incrementally. Each cycle of TDD is very short and provides rapid feedback to developer if the code is working.</p>
<h3>Integrated Development Environment (IDE)</h3>
<p> Modern IDEs are built to shorten development cycle by providing rapid feedback to developer such as syntax warnings, errors and integration to testing, debugging, static analysis, deploying and other tools. The productivity of developer increases when the cycle from edit, build to test, debug or deploy is short.</p>
<h3>One-on-One vs Annual Reviews</h3>
<p> Unfortunately Annual Reviews are still annual rituals in most companies that provide feedback once a year. Instead weekly one-on-one provide shorter feedback and is more effective.</p>
<h3>Opinions vs Data</h3>
<p> All of us have plenty of opinions which are nothing more than guesses. Modern development methodologies such as Lean software development or Lean startup encourages data-driven approach by executing short experiments and learning from the experiments. </p>
<h3>Summary</h3>
<p> We live in rapidly changing knowledge economy. We need to learn how to be nimble and to create a culture that speeds up learning process by performing short experiments. Instead of working in vacuum, we need to validate our assumptions and guesses with actual experiments and take data-driven approach to test our hypothesis. This approach is more bottom-up approach but it doesn&#8217;t mean that we don&#8217;t have a vision. It just means, we are continuously learning, improving and adopting as our environment changes.</p>
]]></content:encoded>
			<wfw:commentRss>http://weblog.plexobject.com/?feed=rss2&amp;p=1692</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Building custom split view controller and tab view controller with horizontal/vertical orientations for iPhone and iPad</title>
		<link>http://weblog.plexobject.com/?p=1691</link>
		<comments>http://weblog.plexobject.com/?p=1691#comments</comments>
		<pubDate>Sat, 10 Mar 2012 10:49:01 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[iPhone development]]></category>

		<guid isPermaLink="false">http://weblog.plexobject.com/?p=1691</guid>
		<description><![CDATA[The iPad platform supports split view controller (<a href="https://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UISplitViewController_class/Reference/Reference.html">UISplitViewController</a>), which is often used to present master-detai]]></description>
			<content:encoded><![CDATA[<p>Over the last few years, I have been developing iPhone and iPad applications and one of recurring problem is managing how to present information in flexible way that works on both iPhone and iPad. The iOS platform uses <a href="https://developer.apple.com/library/ios/#documentation/uikit/reference/UITabBarController_Class/Reference/Reference.html">UITabBarController</a> to organize controllers, however it also comes with a number of limitations such as it only show five tabs on iPhone and though you can add more tabs but you have to go through &#8220;More&#8221; tab to access them. Also, it always show the tabs on the bottom and you cannot change their position.</p>
<p> I would show how you can develop a customized tab controller that works on both iPad and iPhone and supports multiple orientations. </p>
<h3>Custom Split View Controller</h3>
<p> The first thing I needed was to split screen between tab-bars and the main view and though iPad platform supports split view controller (<a href="https://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UISplitViewController_class/Reference/Reference.html">UISplitViewController</a>), however, there are several limitations of the builtin UISplitViewController class such as it only works on iPad in landscape mode and it does not work in iPhone.  Here is an example of customized split view controller:</p>
<pre class="brush: c">
 #import "MasterDetailSplitController.h"
 #import "MenuViewController.h"
 #import "MenuCellView.h"

 @implementation MasterDetailSplitController

 @synthesize menuViewController = _menuViewController;
 @synthesize detailsViewController = _detailsViewController;
 @synthesize menuView = _menuView;
 @synthesize detailsView = _detailsView;

 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
     if((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
     }
     return self;
 }

 - (void)dealloc {
     [_menuViewController release];
     [_detailsViewController release];
     [_menuView release];
     [_detailsView release];
     [super dealloc];
 }

 #pragma mark - View lifecycle

 - (void)viewDidLoad {
     [super viewDidLoad];
     isPortrait = YES;

     self.menuViewController = [[[MenuViewController alloc] initWithSplit:self] autorelease];
     self.menuViewController.view.frame = CGRectMake(0, 0, self.menuView.frame.size.width, self.menuView.frame.size.height);
     [self.menuView addSubview:self.menuViewController.view];
     self.menuView.transform = CGAffineTransformMakeRotation(-M_PI * 0.5);
     self.menuView.autoresizesSubviews = YES;
     self.menuView.backgroundColor = [UIColor grayColor];
     [self layoutSubviews];

 }

 - (void)viewDidUnload {
     [super viewDidUnload];
     self.menuViewController = nil;
     self.detailsViewController = nil;
     self.detailsView = nil;
 }

 - (void) pushToDetailController:(UIViewController *)controller {
     if (self.detailsViewController == controller) {
         return;
     }
     if (self.detailsViewController != nil &#038;&#038; [self.detailsViewController isKindOfClass:[UINavigationController class]]) {
         UINavigationController *navCtr = (UINavigationController *)self.detailsViewController;
         if ([navCtr.viewControllers containsObject:controller]) {
             return;
         }
     }
     for (UIView *v in self.detailsView.subviews) {
         [v removeFromSuperview];
     }
     if (![controller isKindOfClass:[UINavigationController class]]) {
         UINavigationController *navCtr = [[[UINavigationController alloc] initWithRootViewController:controller] autorelease];
         [navCtr setDelegate:self];
         controller = navCtr;
     }

     [self layoutSubviews];

     controller.view.frame = CGRectMake(0, 0, self.detailsView.frame.size.width, self.detailsView.frame.size.height);
     controller.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth
     | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin
     | UIViewAutoresizingFlexibleRightMargin  | UIViewAutoresizingFlexibleLeftMargin;

     self.detailsView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
     self.detailsViewController = controller;
     [self.detailsView addSubview:controller.view];

 }

 #pragma mark -
 #pragma mark STANDARD METHODS

 - (void) viewWillAppear:(BOOL)animated {
     [super viewWillAppear:animated];

     [self.menuViewController viewWillAppear:animated];
     [self.detailsViewController viewWillAppear:animated];
 }

 - (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
     [self.menuViewController viewDidAppear:animated];
     [self.detailsViewController viewDidAppear:animated];
 }

 - (void)viewWillDisappear:(BOOL)animated {
     [super viewWillDisappear:animated];
     [self.menuViewController viewWillDisappear:animated];
     [self.detailsViewController viewWillDisappear:animated];
 }

 - (void)viewDidDisappear:(BOOL)animated {
     [super viewDidDisappear:animated];
     [self.menuViewController viewDidDisappear:animated];
     [self.detailsViewController viewDidDisappear:animated];
 }

 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
     return YES;
 }

 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
     isPortrait = toInterfaceOrientation == UIInterfaceOrientationPortrait || toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown;
     if (isPortrait) {
         self.menuView.transform = CGAffineTransformMakeRotation(-M_PI * 0.5);
     } else {
         self.menuView.transform = CGAffineTransformIdentity;
     }
     [self.menuViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
     [self.detailsViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
 }

 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
     isPortrait = fromInterfaceOrientation != UIInterfaceOrientationPortrait &#038;&#038; fromInterfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
     [self layoutSubviews];
     [self.menuViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
     [self.detailsViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
 }

 - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
     [self.menuViewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
     [self.detailsViewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
 }

 - (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
     [self.menuViewController willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
     [self.detailsViewController willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
 }

 - (void)didAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
     [self.menuViewController didAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation];
     [self.detailsViewController didAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation];
 }

 - (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration {
     [self.menuViewController willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration];
     [self.detailsViewController willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration];
 }

 #pragma mark -
 #pragma mark Helpers
 - (CGSize) sizeRotated {
     UIScreen *screen = [UIScreen mainScreen];
     CGRect bounds = screen.bounds;
     CGRect appFrame = screen.applicationFrame;
     CGSize size = bounds.size;

     float statusBarHeight = MAX((bounds.size.width - appFrame.size.width), (bounds.size.height - appFrame.size.height));

     if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
     {
         // we're going to landscape, which means we gotta swap them
         size.width = bounds.size.height;
         size.height = bounds.size.width;
     }

     size.height = size.height - statusBarHeight -self.tabBarController.tabBar.frame.size.height;
     return size;
 }

 - (void) layoutSubviews {
     CGSize size = [self sizeRotated];
     if (isPortrait) {
         self.detailsView.frame = CGRectMake(0, 0, size.width, size.height-kMENU_CELL_HEIGHT);
         self.menuView.frame = CGRectMake(0.0, size.height-kMENU_CELL_HEIGHT, size.width, kMENU_CELL_HEIGHT);
     } else {
         self.menuView.frame = CGRectMake(0.0, 0.0, kMENU_CELL_WIDTH, size.height);
         self.detailsView.frame = CGRectMake(kMENU_CELL_WIDTH, 0, size.width-kMENU_CELL_WIDTH, size.height);
     }
 }

 - (void) loadView {
     CGSize size = [self sizeRotated];

     UIView *view = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)] autorelease];
     view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
     self.view = view;

     self.menuView = [[[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, kMENU_CELL_WIDTH, size.height)] autorelease];
     self.menuView.autoresizesSubviews = YES;
     self.menuView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleHeight;
     self.menuView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:1.000];
     self.menuView.contentMode = UIViewContentModeScaleToFill;
     self.detailsView = [[[UIView alloc] initWithFrame:CGRectMake(kMENU_CELL_WIDTH, 0, size.width-kMENU_CELL_WIDTH, size.height)] autorelease];
     self.detailsView.autoresizesSubviews = YES;
     self.detailsView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
     self.detailsView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:1.000];
     self.detailsView.contentMode = UIViewContentModeScaleToFill;

     [self.view addSubview:self.menuView];
     [self.view addSubview:self.detailsView];
 }

 @end
 </pre>
<p> You can customize offset of split controller by setting values for kMENU_CELL_WIDTH and kMENU_CELL_HEIGHT. Note that split view controller transforms the menu-view controller and rotates it 90&#8243; when orientation changes to portrait so that table can be scrolled horizontally.</p>
<h3>Building Menu Controller, that can scroll vertically and horizontally</h3>
<p> Next, you need a <a href="https://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UITableViewController_Class/Reference/Reference.html">UITableViewController</a>, which can scroll horizontal and vertically. You can use transform to change the view such as:</p>
<pre class="brush: c">
 #import "MenuViewController.h"
 #import "SampleTableController.h"
 #import "Configuration.h"
 #import "MenuCellView.h"
 #import "MenuInfo.h"
 #import "CustomNavigationController.h"

 @implementation MenuViewController
 @synthesize menuItems = _menuItems;
 @synthesize masterDetailSplitController = _masterDetailSplitController;

 - (id)initWithSplit:(MasterDetailSplitController*)split {
     self = [super initWithStyle:UITableViewStylePlain];
     if (self) {
         self.masterDetailSplitController = split;
         self.tableView.separatorColor = [UIColor clearColor];
         self.menuItems = [[[NSMutableArray alloc] initWithCapacity:10] autorelease];
     }
     return self;
 }

 -(void)refresh {
     MenuInfo *menu = nil;
     [self.menuItems removeAllObjects];
     CustomNavigationController *settingsCtr = [[[CustomNavigationController alloc] initWithStyle:UITableViewStylePlain] autorelease];
     menu = [[[MenuInfo alloc] initWithLabel:@"Settings" andOnImage:@"onSettings.png" andOffImage:@"offSettings.png" andController:settingsCtr] autorelease];
     [self.menuItems addObject:menu];

     NSArray *orderList = [[Configuration sharedConfiguration] getNavigationOrder];
     for (NSString *order in orderList) {
         MenuKind kind = (MenuKind) [order intValue];
         SampleTableController *ctr = [[[SampleTableController alloc] initWithStyle:UITableViewStylePlain] autorelease];
         switch (kind) {
             case ONE:
                 ctr.heading = @"One";
                 menu = [[[MenuInfo alloc] initWithLabel:@"One" andOnImage:@"onDown.png" andOffImage:@"offDown.png" andController:ctr] autorelease];
                 break;
             case TWO:
                 ctr.heading = @"Two";
                 menu = [[[MenuInfo alloc] initWithLabel:@"Two" andOnImage:@"onDownLeft.png" andOffImage:@"offDownLeft.png" andController:ctr] autorelease];
                 break;
             case THREE:
                 ctr.heading = @"Three";
                 menu = [[[MenuInfo alloc] initWithLabel:@"Three" andOnImage:@"onDownRight.png" andOffImage:@"offDownRight.png" andController:ctr] autorelease];
                 break;
             case FOUR:
                 ctr.heading = @"Four";
                 menu = [[[MenuInfo alloc] initWithLabel:@"Four" andOnImage:@"onLeft.png" andOffImage:@"offLeft.png" andController:ctr] autorelease];
                 break;
             case FIVE:
                 ctr.heading = @"Five";
                 menu = [[[MenuInfo alloc] initWithLabel:@"Five" andOnImage:@"onNext.png" andOffImage:@"offNext.png" andController:ctr] autorelease];
                 break;
             case SIX:
                 ctr.heading = @"Six";
                 menu = [[[MenuInfo alloc] initWithLabel:@"Six" andOnImage:@"onPrevious.png" andOffImage:@"offPrevious.png" andController:ctr] autorelease];
                 break;
             case SEVEN:
                 ctr.heading = @"Seven";
                 menu = [[[MenuInfo alloc] initWithLabel:@"Seven" andOnImage:@"onRight.png" andOffImage:@"offRight.png" andController:ctr] autorelease];
                 break;
             case EIGHT:
                 ctr.heading = @"Eight";
                 menu = [[[MenuInfo alloc] initWithLabel:@"Eight" andOnImage:@"onUp.png" andOffImage:@"offUp.png" andController:ctr] autorelease];
                 break;
             case NINE:
                 ctr.heading = @"Nine";
                 menu = [[[MenuInfo alloc] initWithLabel:@"Nine" andOnImage:@"onUpLeft.png" andOffImage:@"offUpLeft.png" andController:ctr] autorelease];
                 break;
             case TEN:
                 ctr.heading = @"Ten";
                 menu = [[[MenuInfo alloc] initWithLabel:@"Ten" andOnImage:@"onUpRight.png" andOffImage:@"offUpRight.png" andController:ctr] autorelease];
                 break;
         }
         [self.menuItems addObject:menu];
     }
     selected = 0;
     [self.tableView reloadData];
 }

 - (UIViewController *) selectedController {
     MenuInfo *info = [self.menuItems objectAtIndex:selected];
     return info.controller;
 }

 - (void)dealloc
 {
     [_masterDetailSplitController release];
     [_menuItems release];
     [super dealloc];
 }

 #pragma mark - View lifecycle

 - (void)viewDidLoad
 {
     [super viewDidLoad];
     isPortrait = YES;
     UIView *bgView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
     bgView.transform = CGAffineTransformMakeRotation(M_PI * 0.5);
     self.tableView.backgroundView = bgView;
     self.view.backgroundColor = [UIColor blackColor];
 }

 - (void)viewWillAppear:(BOOL)animated
 {
     [super viewWillAppear:animated];
     self.tableView.showsVerticalScrollIndicator = NO;
     self.tableView.showsHorizontalScrollIndicator = NO;
     self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
     self.tableView.separatorColor = [UIColor clearColor];

     [self refresh];
     [self.masterDetailSplitController pushToDetailController:[self selectedController]];    

 }

 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
 {
     return YES;
 }

 -(NSArray *)allControllers {
     NSMutableArray *list = [[[NSMutableArray alloc] initWithCapacity:10] autorelease];
     for (MenuInfo *info in self.menuItems) {
         [list addObject:info.controller];
     }
     return list;
 }

 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
     isPortrait = toInterfaceOrientation == UIInterfaceOrientationPortrait || toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown;

     for (UIViewController *ctr in [self allControllers]) {
         [ctr willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
     }
     [self.tableView reloadData];
 }

 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
     for (UIViewController *ctr in [self allControllers]) {
         [ctr didRotateFromInterfaceOrientation:fromInterfaceOrientation];
     }
 }

 - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
     for (UIViewController *ctr in [self allControllers]) {
         [ctr willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
     }
 }

 - (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
     for (UIViewController *ctr in [self allControllers]) {
         [ctr willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
     }
 }

 - (void)didAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
     for (UIViewController *ctr in [self allControllers]) {
         [ctr didAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation];
     }
 }

 - (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration {
     for (UIViewController *ctr in [self allControllers]) {
         [ctr willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration];
     }
 }

 #pragma mark - Table view data source

 - (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView
 {
     return 1;
 }

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
 {
     return [self.menuItems count];
 }

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 {
     static NSString *CellIdentifier = @"MenuCell";
     MenuCellView *cell = (MenuCellView *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
     if (cell == nil) {
         cell = [[[MenuCellView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
     }
     MenuInfo *menu = [self.menuItems objectAtIndex:indexPath.row];
     NSString *imageName = selected == indexPath.row ? menu.onImage : menu.offImage;
     cell.imageView.image = [UIImage imageNamed:imageName];

     if (isPortrait) {
         cell.transform = CGAffineTransformMakeRotation(M_PI * 0.5);
     } else {
         cell.transform = CGAffineTransformIdentity;
     }

     return cell;
 }

 /**
  * Displays the specified controller view
  */
 -(void) displayControllerView:(UIViewController*)controller {
     if (controller != nil) {
         [self.masterDetailSplitController pushToDetailController:controller];
     }
     [self.tableView reloadData];
 }

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     selected = indexPath.row;

     UIViewController *controller = [self selectedController];

     [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
     [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

     [self displayControllerView:controller];
 }

 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
     if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
         return kMENU_CELL_WIDTH * 3;
     } else {
         return kMENU_CELL_WIDTH;
     }
 }

 @end
 </pre>
<p> The height of cells for iPad is stretched so that you can test scrolling across the screen. The cells are also rotated 90&#8243; when orientation is in portrait mode so that they are displayed properly on the horizontal table. Also, if you have a background image, then you will need to rotate it as well.</p>
<h3>Customizing order of tabs</h3>
<p> You can also allow users to change the order by storing order in a configuration, here is an example of controller that allows reordering:</p>
<pre class="brush: c">
 #import "CustomNavigationController.h"
 #import "MenuViewController.h"
 #import "Configuration.h"
 #import "AppDelegate.h"

 @implementation CustomNavigationController

 @synthesize controllers;

 static NSArray *kControllerNames;

 + (void) initialize {
     kControllerNames = [[NSArray arrayWithObjects:@"One", @"Two", @"Three", @"Four", @"Five", @"Six", @"Seven", @"Eight", @"Nine", @"Ten", nil] retain];
 }

 - (id)initWithStyle:(UITableViewStyle)style
 {
     self = [super initWithStyle:style];
     if (self) {
     }
     return self;
 }

 #pragma mark - View lifecycle

 - (void)viewDidLoad
 {
     [super viewDidLoad];
     controllers = [[NSMutableArray alloc] initWithCapacity:10];
     [super setEditing:YES animated:YES];
     [self.tableView setEditing:YES animated:YES];
     self.navigationItem.title = @"Navigation";
     self.tableView.backgroundColor = [UIColor clearColor];
     [self.tableView setIndicatorStyle:UIScrollViewIndicatorStyleWhite];
     self.tableView.separatorColor = [UIColor blackColor];
 }

 -(void)viewWillAppear:(BOOL)animated {
     [super viewWillAppear:animated];

     [controllers removeAllObjects];
     NSArray *list = [[Configuration sharedConfiguration] getNavigationOrder];
     for (NSString *ctr in list) {
         [controllers addObject:[NSNumber numberWithInt:[ctr intValue]]];
     }

     [self.tableView reloadData];
 }

 - (void)viewDidUnload
 {
     [super viewDidUnload];
     self.controllers = nil;
 }

 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
 {
     return YES;
 }

 #pragma mark - Table view data source

 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
 {
     return 1;
 }

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
 {
     return [controllers count];
 }

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 {
     static NSString *CellIdentifier = @"CustomNavigationCellView";

     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
     if (cell == nil) {
         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
     }

     NSNumber *num = [controllers objectAtIndex:indexPath.row];
     cell.textLabel.text = [kControllerNames objectAtIndex:[num intValue]];

     return cell;
 }

 #pragma mark - Table view delegate

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
 {

 }

 - (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
     return UITableViewCellEditingStyleNone;
 }

 - (void)tableView:(UITableView *)aTableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
     [self.tableView reloadData];
 }

 #pragma mark Row reordering
 - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
     return YES;
 }

 - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
       toIndexPath:(NSIndexPath *)toIndexPath {
     NSNumber *item = [[controllers objectAtIndex:fromIndexPath.row] retain];
     [controllers removeObject:item];
     [controllers insertObject:item atIndex:toIndexPath.row];
     [item release];

     NSMutableArray *list = [[[NSMutableArray alloc] initWithCapacity:10] autorelease];
     for (NSNumber *num in controllers) {
         [list addObject:[num stringValue]];
     }
     [[Configuration sharedConfiguration] setNavigationOrder:list];

     AppDelegate *delegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
     [delegate.splitViewController.menuViewController refresh];
 }

 - (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
     return NO;
 }

 -(void)dealloc {
     [controllers release];
     [super dealloc];
 }
 @end
 </pre>
<p> Note that we are storing the order of tabs in the configuration and saving it when user moves the navigation items.</p>
<h3>Screen shots</h3>
<p><img src="https://github.com/bhatti/SplitControllerAndCustomNavigation/raw/master/iphone_portrait.png" alt="iPhone Portrait"></p>
<p><img src="https://github.com/bhatti/SplitControllerAndCustomNavigation/raw/master/iphone_landscape.png" alt="iPhone Landscape"></p>
<p><img src="https://github.com/bhatti/SplitControllerAndCustomNavigation/raw/master/ipad_portrait.png" alt="iPad Portrait"></p>
<p><img src="https://github.com/bhatti/SplitControllerAndCustomNavigation/raw/master/ipad_landscape.png" alt="iPad Landscape"></p>
<p>(Note that iPad images are stretched to show that they are horizontal and vertical scrollable)</p>
<h3>Download the code</h3>
<p> I am not showing all code here but the full code is available from <a href="https://github.com/bhatti/SplitControllerAndCustomNavigation">https://github.com/bhatti/SplitControllerAndCustomNavigation</a>. I hope you find it useful.</p>
<p> <script type="text/javascript">
      SyntaxHighlighter.all()
 </script> </p>
<div style='background-color: white'>
<div id="fb-root">
 </div>
<p> <script>
      window.fbAsyncInit = function() {
           FB.init({appId: '119853371386770', status: true, cookie: true, xfbml: true});
         };
         (function() {
           var e = document.createElement('script'); e.async = true;
           e.src = document.location.protocol +
             '//connect.facebook.net/en_US/all.js';
           document.getElementById('fb-root').appendChild(e);
         }());
 </script></p>
<div id="facebook">
   <fb:comments xid="SplitControllerAndCustomNavigation" simple="1" publish_feed="false" width="642" title='a comment on "Custom Split Controller and Horizontal Tab Bar Controller"'><br />
   </fb:comments>
 </div>
</p></div>
]]></content:encoded>
			<wfw:commentRss>http://weblog.plexobject.com/?feed=rss2&amp;p=1691</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Using self-signed certificates and resolving SSL version with iOS5 SDK</title>
		<link>http://weblog.plexobject.com/?p=1690</link>
		<comments>http://weblog.plexobject.com/?p=1690#comments</comments>
		<pubDate>Wed, 16 Nov 2011 07:32:02 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[iPhone development]]></category>

		<guid isPermaLink="false">http://weblog.plexobject.com/?p=1690</guid>
		<description><![CDATA[<h2>Using Self-signed certificates with iOS5</h2>
 There have been a few changes to SSL in iOS5 SDK, which caused some snafus. We have been using ASIHTTPRequest library for accessing our web services that require SSL, however we use self-signed certificat]]></description>
			<content:encoded><![CDATA[<h2>Using Self-signed certificates with iOS5</h2>
<p> There have been a few changes to SSL in iOS5 SDK, which caused some snafus to our iPhone app. We have been using ASIHTTPRequest library for accessing our web services that require SSL and we use self-signed certificates in test environment. I noticed that the iPhone app wasn&#8217;t able to connect to the server after upgrading to iOS5 SDK. The original code in ASIHTTPRequest.m that accepted self-signed certificates looked like:</p>
<pre class="brush: c">
  NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
                                       [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
                                       [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
                                       [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
                                       kCFNull,kCFStreamSSLPeerName, nil];

   CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, (CFTypeRef)sslProperties);
 </pre>
<p> However, the iOS5 SDK deprecated kCFStreamPropertySSLPeerCertificates, kCFStreamSSLAllowsExpiredCertificates, kCFStreamSSLAllowsExpiredRoots, kCFStreamSSLAllowsAnyRoot and requires using kCFStreamSSLValidatesCertificateChain to disable certificates, e.g.:</p>
<pre class="brush: c">

   CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings,
         [NSMutableDictionary dictionaryWithObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]);
   [[self readStream] setProperty:[NSDictionary dictionaryWithObjectsAndKeys:
         (id)kCFBooleanFalse, kCFStreamSSLValidatesCertificateChain, nil] forKey:(NSString *)kCFStreamPropertySSLSettings];
</pre>
<p> Here is a complete example if you need to skip validation without using ASIHTTPRequest:</p>
<pre class="brush: c">
 - (void)start {
     CFStringRef bodyString = CFSTR("xml....");
     CFDataRef bodyData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, bodyString, kCFStringEncodingUTF8, 0);

     CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("POST"), (CFURLRef) [NSURL URLWithString:@"https://optionshouse.com:443/m?"], kCFHTTPVersion1_1);
     CFHTTPMessageSetBody(request, bodyData);

     CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(NULL, request);

     self.inputStream = (NSInputStream *) readStream;

     [self.inputStream setProperty:[NSDictionary dictionaryWithObjectsAndKeys:(id) kCFBooleanFalse,    kCFStreamSSLValidatesCertificateChain, nil ] forKey:(NSString *) kCFStreamPropertySSLSettings];

     [self.inputStream setDelegate:self];
     [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
     [self.inputStream open];

     CFRelease(readStream);
     CFRelease(request);
 }

 - (void)stop {
     [self.inputStream setDelegate:nil];
     [self.inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
     [self.inputStream close];
     self.inputStream = nil;
 }

 - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
     switch (eventCode) {
         case NSStreamEventOpenCompleted: {
             NSLog(@"open");
         } break;
         case NSStreamEventHasBytesAvailable: {
             NSInteger   bytesRead;
             uint8_t     junk[1024];

             NSLog(@"has bytes");
             bytesRead = [self.inputStream read:junk maxLength:sizeof(junk)];
             if (bytesRead == 0) {
                 NSLog(@"read end");
                 [self stop];
             } else if (bytesRead < 0) {
                 NSLog(@"read error");
                 [self stop];
             } else {
                 NSString *string = [[[NSString alloc] initWithBytes:junk
                                                         length:bytesRead
                                                       encoding:NSUTF8StringEncoding] autorelease];
                 NSLog(@"Read %@", string);
             }
         } break;
         case NSStreamEventErrorOccurred: {
             NSError * error = [self.inputStream streamError];
             NSLog(@"error %@ / %zd", [error domain], (ssize_t) [error code]);
             [self stop];
         } break;
         case NSStreamEventEndEncountered: {
             NSLog(@"end");
             [self stop];
         } break;
         default: {
             assert(NO);
             [self stop];
         } break;
     }
 }
</pre>
<h2>Using SSL with old F5 load balancers</h2>
<p> Another issue I found with iOS5 was that despite using valid certificate in production environment, the SSL was still not working. Based on <a href="http://developer.apple.com/library/ios/#technotes/tn2287/_index.html#//apple_ref/doc/uid/DTS40011309">Apple's documentation</a>, it turned out that the default version for SSL has been changed to TLS 1.0 and our production environment used F5 load balancer, which required SSLv3. So, I had to explicitly specify SSL version in ASIHTTPRequest.m </p>
<pre class="brush: c">
     const void* keys[] = { kCFStreamSSLLevel };
     // kCFStreamSocketSecurityLevelTLSv1_0SSLv3 configures max TLS 1.0, min SSLv3
     // (same as default behavior on versions before iOS 5).
     // kCFStreamSocketSecurityLevelTLSv1_0 configures to use only TLS 1.0.
     // kCFStreamSocketSecurityLevelTLSv1_1 configures to use only TLS 1.1.
     // kCFStreamSocketSecurityLevelTLSv1_2 configures to use only TLS 1.2.
     const void* values[] = { CFSTR("kCFStreamSocketSecurityLevelTLSv1_0SSLv3") };
     CFDictionaryRef sslSettingsDict = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, &#038;kCFTypeDictionaryKeyCallBacks, &#038;kCFTypeDictionaryValueCallBacks);
     CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslSettingsDict);
     CFRelease(sslSettingsDict);
 </pre>
<p> With these changes, our iPhone app was working as expected. </p>
<h3>Acknowledgement</h3>
<p> Many thanks for Apple Engineer "Quinn", who helped resolving the SSL issues.</p>
<p> <script type="text/javascript">
      SyntaxHighlighter.all()
 </script> </p>
<div style='background-color: white'>
<div id="fb-root">
 </div>
<p> <script>
      window.fbAsyncInit = function() {
           FB.init({appId: '119853371386770', status: true, cookie: true, xfbml: true});
         };
         (function() {
           var e = document.createElement('script'); e.async = true;
           e.src = document.location.protocol +
             '//connect.facebook.net/en_US/all.js';
           document.getElementById('fb-root').appendChild(e);
         }());
 </script></p>
<div id="facebook">
   <fb:comments xid="ExpiredCertsWithiPhoneSDK5" simple="1" publish_feed="false" width="642" title='a comment on "Using self-signed certificates and resolving SSL version with iOS5 SDK"'><br />
   </fb:comments>
 </div>
</p></div>
]]></content:encoded>
			<wfw:commentRss>http://weblog.plexobject.com/?feed=rss2&amp;p=1690</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
