| <?xml version="1.0" encoding="utf-8"?>
|
| <!-- |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| --> |
| <!-- Author: Christophe Coenraets - http://coenraets.org -->
|
| <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
|
| xmlns:s="library://ns.adobe.com/flex/spark"
|
| xmlns:mx="library://ns.adobe.com/flex/mx"
|
| xmlns:parsley="http://www.spicefactory.org/parsley"
|
| xmlns:controls="flex.skeletons.traderdesktop.controls.*"
|
| width="100%" height="100%">
|
|
|
| <fx:Script>
|
| <![CDATA[
|
| import flex.skeletons.traderdesktop.model.Stock;
|
| import flex.skeletons.traderdesktop.skins.SidebarPanelSkin;
|
|
|
| import mx.messaging.AdvancedChannelSet;
|
| import mx.messaging.Channel;
|
| import mx.messaging.channels.AMFChannel;
|
| import mx.messaging.channels.RTMPChannel;
|
| import mx.messaging.channels.StreamingAMFChannel;
|
| import mx.messaging.events.MessageEvent;
|
| import mx.rpc.AsyncResponder;
|
| import mx.rpc.AsyncToken;
|
| import mx.rpc.events.FaultEvent;
|
| import mx.rpc.events.ResultEvent;
|
| import mx.rpc.remoting.RemoteObject;
|
|
|
| protected var streamingAMFChannel:StreamingAMFChannel;
|
| protected var rtmpChannel:RTMPChannel;
|
| protected var amfChannel:AMFChannel;
|
|
|
| [Bindable]
|
| protected var stockList:ArrayCollection;
|
|
|
| protected var stockMap:Dictionary;
|
|
|
| private var _feedService:RemoteObject;
|
|
|
| [Inject]
|
| public function set channels(channels:ArrayCollection):void
|
| {
|
| comboChannels.dataProvider = channels;
|
| if (channels && channels.length>0)
|
| {
|
| comboChannels.selectedIndex = 0;
|
| }
|
| }
|
|
|
| [Inject]
|
| public function set feedService(service:RemoteObject):void
|
| {
|
| _feedService = service;
|
| getFeedStatus();
|
| }
|
|
|
| public function get feedService():RemoteObject
|
| {
|
| return _feedService;
|
| }
|
|
|
| protected var msgCount:int=0;
|
|
|
| protected var totalLatency:Number = 0;
|
|
|
| protected function messageHandler(event:MessageEvent):void
|
| {
|
| msgCount++;
|
| var latency:Number = new Date().time - event.message.timestamp;
|
| totalLatency += latency;
|
| latencyLbl.text = ""+latency;
|
|
|
| var changedStock:Stock = event.message.body as Stock;
|
| var stock:Stock = stockMap[changedStock.symbol];
|
| if (stock)
|
| {
|
| stock.open = changedStock.open;
|
| stock.change = changedStock.change;
|
| stock.last = changedStock.last;
|
| stock.high = changedStock.high;
|
| stock.low = changedStock.low;
|
| stock.date = changedStock.date;
|
| }
|
| }
|
|
|
| public function get targetFrameRate():int
|
| {
|
| return nsFrameRate.value;
|
| }
|
|
|
| protected function getFeedStatus():void
|
| {
|
| var token:AsyncToken = feedService.getFeedStatus();
|
| token.addResponder(new AsyncResponder(getFeedStatus_result, getFeedStatus_fault));
|
| }
|
|
|
| protected function startFeed():void
|
| {
|
| var token:AsyncToken = feedService.start(nsNumStocks.value, nsNumThreads.value, nsSleepMillis.value);
|
| token.addResponder(new AsyncResponder(startFeed_result, startFeed_fault));
|
| }
|
|
|
| protected function stopFeed():void
|
| {
|
| if (consumer.subscriptions.length>0)
|
| unsubscribe();
|
| var token:AsyncToken = feedService.stop();
|
| token.addResponder(new AsyncResponder(stopFeed_result, stopFeed_fault));
|
| stockList = null;
|
| }
|
|
|
| protected function getStocks():void
|
| {
|
| var token:AsyncToken = feedService.getStocks();
|
| token.addResponder(new AsyncResponder(getStocks_result, getStocks_fault));
|
| }
|
|
|
| protected function getFeedStatus_result(event:ResultEvent, token:AsyncToken):void
|
| {
|
| var status:Object = event.result;
|
| if (status.running)
|
| {
|
| log("The server feed is running");
|
| log("Stocks: " + status.numStocks + " Threads: " + status.numThreads + " sleepTime: " + status.sleepTime);
|
| nsNumStocks.value = status.numStocks;
|
| nsNumThreads.value = status.numThreads;
|
| nsSleepMillis.value = status.sleepTime;
|
| getStocks();
|
| }
|
| else
|
| {
|
| log("The server feed is not running");
|
| }
|
| }
|
|
|
| protected function getFeedStatus_fault(event:FaultEvent, token:AsyncToken):void
|
| {
|
| log("Make sure you started the server.\n" + event.fault.faultString);
|
| }
|
|
|
| protected function startFeed_result(event:ResultEvent, token:AsyncToken):void
|
| {
|
| log("Feed started successfully");
|
| getStocks();
|
| }
|
|
|
| protected function startFeed_fault(event:FaultEvent, token:AsyncToken):void
|
| {
|
| log("The request to start the feed failed.\n" + event.fault.faultString);
|
| }
|
|
|
| protected function stopFeed_result(event:ResultEvent, token:AsyncToken):void
|
| {
|
| log("Feed stopped successfully");
|
| }
|
|
|
| protected function stopFeed_fault(event:FaultEvent, token:AsyncToken):void
|
| {
|
| log("The request to stop the feed failed.\n" + event.fault.faultString);
|
| }
|
|
|
| protected function getStocks_result(event:ResultEvent, token:AsyncToken):void
|
| {
|
| stockList = event.result as ArrayCollection;
|
| stockMap = new Dictionary();
|
| var stock:Stock;
|
| for (var i:int=0; i<stockList.length; i++)
|
| {
|
| stock = stockList.getItemAt(i) as Stock;
|
| stockMap[stock.symbol] = stock;
|
| }
|
| if (cbRender.selected)
|
| dg.dataProvider = stockList;
|
| }
|
|
|
| protected function getStocks_fault(event:FaultEvent, token:AsyncToken):void
|
| {
|
| log("Error: " + event.fault.faultString);
|
| }
|
|
|
| protected function startBenchmark():void
|
| {
|
| var numStocks:int = nsNumStocks.value;
|
| var numThreads:int = nsNumThreads.value;
|
| var sleepMillis:int = nsSleepMillis.value;
|
| var timer:Timer = new Timer(60000, 1);
|
| var benchmark:Object = {
|
| stocks: numStocks,
|
| threads: numThreads,
|
| sleep: sleepMillis,
|
| throughput: nf1.format(numThreads*1000/sleepMillis),
|
| channel: comboChannels.selectedItem.id,
|
| maxFrequency: nsMaxClientFrequency.value,
|
| frameRate: nsFrameRate.value,
|
| rendering: cbRender.selected,
|
| messageCount: "...",
|
| mps: "...",
|
| latency: "..."};
|
| benchmarks.addItem(benchmark);
|
| timer.addEventListener(TimerEvent.TIMER_COMPLETE,
|
| function():void
|
| {
|
| benchmark.messageCount = nf0.format(msgCount);
|
| benchmark.mps = nf1.format(msgCount / 60);
|
| benchmark.latency = nf1.format(totalLatency / msgCount);
|
| dgPerf.dataProvider = benchmarks;
|
| });
|
| msgCount = 0;
|
| totalLatency = 0;
|
| timer.start();
|
| }
|
|
|
| protected function subscribe():void
|
| {
|
| stage.frameRate = nsFrameRate.value;
|
| var cs:AdvancedChannelSet = new AdvancedChannelSet();
|
| cs.addChannel(comboChannels.selectedItem as Channel);
|
| consumer.channelSet = cs;
|
| consumer.maxFrequency = nsMaxClientFrequency.value;
|
| addSubscriptions();
|
| consumer.subscribe();
|
| }
|
|
|
| protected function unsubscribe():void
|
| {
|
| var stock:Stock;
|
| for (var i:int=0; i<stockList.length; i++)
|
| {
|
| stock = stockList.getItemAt(i) as Stock;
|
| consumer.removeSubscription(stock.symbol);
|
| }
|
| log("Unsubscribed from " + stockList.length + " stocks");
|
| consumer.unsubscribe();
|
| consumer.disconnect();
|
| }
|
|
|
| protected function addSubscriptions():void
|
| {
|
| var stock:Stock;
|
| for (var i:int=0; i<stockList.length; i++)
|
| {
|
| stock = stockList.getItemAt(i) as Stock;
|
| consumer.addSubscription(stock.symbol);
|
| }
|
| log("Subscribed to " + stockList.length + " stocks");
|
| }
|
|
|
|
|
| public function closeConnection():void
|
| {
|
| consumer.unsubscribe();
|
| consumer.disconnect();
|
| }
|
|
|
| protected function log(message:String):void
|
| {
|
| logArea.text += message + "\n";
|
| }
|
|
|
| ]]>
|
| </fx:Script>
|
|
|
| <fx:Declarations>
|
|
|
| <parsley:Configure/>
|
|
|
| <mx:NumberFormatter id="nf0" precision="0"/>
|
| <mx:NumberFormatter id="nf1" precision="1"/>
|
|
|
| <s:ArrayCollection id="benchmarks"/>
|
|
|
| <mx:MultiTopicConsumer id="consumer" destination="f" message="messageHandler(event)"
|
| fault="log(event.faultString)"/>
|
|
|
| </fx:Declarations>
|
|
|
| <s:layout>
|
| <s:HorizontalLayout gap="0"/>
|
| </s:layout>
|
|
|
| <s:SkinnableContainer width="300" height="100%" styleName="sidebar">
|
|
|
| <s:layout>
|
| <s:VerticalLayout paddingTop="8" paddingLeft="8" paddingRight="8" paddingBottom="8"/>
|
| </s:layout>
|
|
|
| <s:Panel title="Server Feed" width="100%" skinClass="flex.skeletons.traderdesktop.skins.SidebarPanelSkin">
|
| <s:layout>
|
| <s:VerticalLayout paddingBottom="4" paddingRight="4" paddingLeft="4"/>
|
| </s:layout>
|
| <mx:Form paddingBottom="4" paddingLeft="4">
|
| <mx:FormItem label="Number of Symbols" width="100%">
|
| <s:NumericStepper id="nsNumStocks" minimum="1" maximum="1000" width="60" stepSize="1" value="100"/>
|
| </mx:FormItem>
|
| <mx:FormItem label="Number of Threads">
|
| <s:NumericStepper id="nsNumThreads" minimum="1" maximum="20" width="60" stepSize="1" value="1"/>
|
| </mx:FormItem>
|
| <mx:FormItem label="Sleep Time (ms)">
|
| <s:NumericStepper id="nsSleepMillis" value="10" stepSize="1" width="60" minimum="1" maximum="1000"/>
|
| </mx:FormItem>
|
| <mx:FormItem label="Server Throughput" paddingTop="8">
|
| <s:Label enabled="false" text="{nf1.format(1000 / nsSleepMillis.value * nsNumThreads.value)} mps"/>
|
| </mx:FormItem>
|
| <mx:FormItem label="Frequency">
|
| <s:Label enabled="false" text="{nf1.format(1000 / nsSleepMillis.value * nsNumThreads.value / nsNumStocks.value)} mps/symbol"/>
|
| </mx:FormItem>
|
| </mx:Form>
|
| <s:controlBarContent>
|
| <s:Button label="Start Feed" click="startFeed()"/>
|
| <s:Button label="Stop Feed" click="stopFeed()"/>
|
| </s:controlBarContent>
|
| </s:Panel>
|
|
|
| <s:Panel title="Client Subscription" width="100%" skinClass="flex.skeletons.traderdesktop.skins.SidebarPanelSkin">
|
| <mx:Form>
|
| <mx:FormItem label="Channel">
|
| <s:ComboBox id="comboChannels" labelField="id" enabled="{!consumer.subscribed}"/>
|
| </mx:FormItem>
|
| <mx:FormItem label="Max Frequency">
|
| <s:NumericStepper id="nsMaxClientFrequency" minimum="0" maximum="5000" value="0" stepSize="1" width="50"/>
|
| </mx:FormItem>
|
| <mx:FormItem label="Frame Rate">
|
| <s:NumericStepper id="nsFrameRate" minimum="1" maximum="300" value="24" stepSize="1" width="50"/>
|
| </mx:FormItem>
|
| <mx:FormItem label="Rendering" paddingTop="8">
|
| <s:CheckBox id="cbRender" click="dg.dataProvider = cbRender.selected ? stockList : null" selected="true"/>
|
| </mx:FormItem>
|
| <mx:FormItem label="Current Latency">
|
| <s:Label id="latencyLbl" text="n/a"/>
|
| </mx:FormItem>
|
| </mx:Form>
|
| <s:controlBarContent>
|
| <s:Button label="Subscribe" click="subscribe()" enabled="{!consumer.subscribed && stockList != null}"/>
|
| <s:Button label="Unsubscribe" click="unsubscribe()" enabled="{consumer.subscribed}"/>
|
| </s:controlBarContent>
|
| </s:Panel>
|
|
|
| <s:Panel title="Log" width="100%" height="100%" skinClass="flex.skeletons.traderdesktop.skins.SidebarPanelSkin">
|
| <s:TextArea id="logArea" width="100%" height="100%" borderVisible="false"
|
| updateComplete="logArea.scroller.verticalScrollBar.value = logArea.scroller.verticalScrollBar.maximum"/>
|
| </s:Panel>
|
|
|
| </s:SkinnableContainer>
|
|
|
| <s:SkinnableContainer width="100%" height="100%" styleName="body">
|
|
|
| <s:layout>
|
| <s:VerticalLayout paddingTop="8" paddingLeft="8" paddingRight="8" paddingBottom="8"/>
|
| </s:layout>
|
|
|
| <controls:FastDataGrid id="dg" width="100%" height="70%" borderVisible="true">
|
| <controls:columns>
|
| <mx:DataGridColumn headerText="Symbol" dataField="symbol"/>
|
| <mx:DataGridColumn headerText="Open" dataField="open" textAlign="right"/>
|
| <mx:DataGridColumn headerText="Last" dataField="last" itemRenderer="flex.skeletons.traderdesktop.renderers.LastRenderer" textAlign="right"/>
|
| <mx:DataGridColumn headerText="Change" dataField="change" itemRenderer="flex.skeletons.traderdesktop.renderers.ChangeRenderer" textAlign="right"/>
|
| <mx:DataGridColumn headerText="High" dataField="high" itemRenderer="flex.skeletons.traderdesktop.renderers.HighRenderer" textAlign="right"/>
|
| <mx:DataGridColumn headerText="Low" dataField="low" itemRenderer="flex.skeletons.traderdesktop.renderers.LowRenderer" textAlign="right"/>
|
| </controls:columns>
|
| </controls:FastDataGrid>
|
|
|
| <s:Panel title="Performance" width="100%" height="30%">
|
| <mx:DataGrid id="dgPerf" dataProvider="{benchmarks}" width="100%" height="100%" borderVisible="false">
|
| <mx:columns>
|
| <mx:DataGridColumn headerText="Symbols" dataField="stocks" textAlign="center" headerWordWrap="true"/>
|
| <mx:DataGridColumn headerText="Threads" dataField="threads" textAlign="center" headerWordWrap="true"/>
|
| <mx:DataGridColumn headerText="Sleep (ms)" dataField="sleep" textAlign="center" headerWordWrap="true"/>
|
| <mx:DataGridColumn headerText="Target Server mps" dataField="throughput" textAlign="center" headerWordWrap="true"/>
|
| <mx:DataGridColumn headerText="Channel" dataField="channel" textAlign="center"/>
|
| <mx:DataGridColumn headerText="Max Freq." dataField="maxFrequency" headerWordWrap="true" textAlign="center"/>
|
| <mx:DataGridColumn headerText="Frame Rate" dataField="frameRate" headerWordWrap="true" textAlign="center"/>
|
| <mx:DataGridColumn headerText="Render" dataField="rendering" textAlign="center"/>
|
| <mx:DataGridColumn headerText="Total msgs" dataField="messageCount" textAlign="center" headerWordWrap="true"/>
|
| <mx:DataGridColumn headerText="Actual Client mps" dataField="mps" textAlign="center" headerWordWrap="true"/>
|
| <mx:DataGridColumn headerText="Avg Latency (ms)" dataField="latency" textAlign="center" headerWordWrap="true"/>
|
| </mx:columns>
|
| </mx:DataGrid>
|
| <s:controlBarContent>
|
| <s:Button label="Run Benchmark" click="startBenchmark()" enabled="{consumer.subscribed}"/>
|
| </s:controlBarContent>
|
| </s:Panel>
|
|
|
| </s:SkinnableContainer>
|
|
|
| </s:Group> |