blob: afc33e907db6c1936512f8a994d38b9217cf99c1 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
////////////////////////////////////////////////////////////////////////////////
package mx.utils
{
import flash.display.DisplayObject;
import flash.utils.getTimer;
import mx.core.UIComponent;
CONFIG::performanceInstrumentation
public final class PerfUtil
{
public function PerfUtil()
{
idVector = new Vector.<String>();
timeVector = new Vector.<uint>();
}
private static var _instance:PerfUtil;
private var idVector:Vector.<String>;
private var timeVector:Vector.<uint>;
private var startSamplingCount:int = 0;
private var detailedSamplingCount:int = 0;
/**
* @return Returns the PerfUtil instance.
*/
static public function getInstance():PerfUtil
{
if (!_instance)
_instance = new PerfUtil();
return _instance;
}
// types of time stamps:
private const MARK:uint = 0x00000000;
private const START:uint = 0x20000000;
private const END:uint = 0x40000000;
private const TEST_CASE_START_ABSOLUTE:uint = 0x60000000;
private const TEST_CASE_START_RELATIVE:uint = 0x80000000;
private const TEST_CASE_END:uint = 0xA0000000;
private const IGNORE:uint = 0xC0000000;
private const TIME_MASK:uint = 0x1FFFFFFF; // 29 bits for time values
private const FLAG_MASK:uint = ~TIME_MASK; // 3 bits for flags
public static var detailedSampling:Boolean = false;
/**
* Starts gathering samples associated with the <code>testCase</code>
*
* @param testCase The name of the test case.
* @param absoluteTime Whether to log samples relative to the startSampling() call or
* relative to the run-time VM boot time.
*/
public function startSampling(testCase:String, absoluteTime:Boolean):void
{
++startSamplingCount;
timeVector.push( absoluteTime ? TEST_CASE_START_ABSOLUTE : TEST_CASE_START_RELATIVE ); // Test case started
idVector.push(testCase);
markTime(testCase);
}
/**
* Stops gathering samples for the specified testCase.
*
* @param testCase The name of the test case.
*/
public function finishSampling(testCase:String):void
{
timeVector.push( TEST_CASE_END ); // Test case finished
idVector.push(testCase);
markTime(testCase);
--startSamplingCount;
}
/**
* Marks the start of an event to be measured. Call right before the event.
*
* @return Returns the token associated with the event being measured. This token is later on
* passed to markEnd() when the event has completed.
*/
public function markStart():int
{
if (startSamplingCount <= 0)
return -1;
var time:uint = flash.utils.getTimer();
idVector.push(null); // mark differently instead of pushing ".start" or ".end" - use the senior 2 bits from the time stamp, avoids unnecessary string operations
// Start
time |= START;
timeVector.push(time);
return timeVector.length - 1;
}
/**
* Marks the end of an event being measured. Call right after the event.
* If the event is logged, tolerance permitting, the summary
* (through getSummary() method) will contain the following two entries:
*
* "[time at markStart] : [name].start\n"
* "[time at markEnt] : [name].end\n"
*
* @param name The event name, will be shown in the log
* @param token The token returned by the markStart() method
* @param tolerance Tolerance in miliseconds. If the event took less than the tolerance,
* it will be omitted from the log.
*/
public function markEnd(name:String, token:int, tolerance:int = 0, idObject:Object = null):void
{
if (startSamplingCount <= 0 || token < 0 )
return;
var time:uint = flash.utils.getTimer();
var startTime:uint = timeVector[token] & TIME_MASK;
if (time - startTime < tolerance)
{
// If the start is the last element, then pop it off the stack,
// otherwise set it to "ignore" (don't delete elements in the middle of the vector)
if (token == timeVector.length - 1)
{
timeVector.pop();
idVector.pop();
}
else
timeVector[token] = IGNORE;
return;
}
if (idObject)
{
if (idObject is DisplayObject)
name = (idObject as DisplayObject).name + name;
else
name = idObject.toString() + name;
}
idVector[token] = name; // Fix the start id
idVector.push(name);
time |= END;
timeVector.push(time);
}
/**
* Adds the following entry to the summary:
*
* "[time at markTime] : [name]\n"
*/
public function markTime(name:String):void
{
if (startSamplingCount <= 0)
return;
var time:int = flash.utils.getTimer();
timeVector.push(time);
idVector.push(name);
}
/**
* Returns the summary for the sampling session.
*
* Example of instrumentation:
* class SystemManager
* {
* SystemManager():void
* {
* PerfUtil.getInstance().startSampling("Application Startup", true);
* PerfUtil.getInstance().markTime("SystemManager c-tor");
* ...
* }
*
* initHandler()
* {
* var token:int = PerfUtil.getInstance().markStart();
* ...
* PerfUtil.getInstance().markEnd("SystemManager.initHandler()", token);
* }
*
* kickOff()
* {
* var token:int = PerfUtil.getInstance().markStart();
* ...
* ResourceManager.getInstance();
* ...
* PerfUtil.getInstance().markEnd("SystemManager.kickOff()", token);
* PerfUtil.getInstacne().finishSampling("Application Startup");
* }
* }
*
* class ResourceManager
* {
* getInstance()
* {
* var token:int = PerfUtil.getInstance().markStart();
* ...
* PerfUtil.getInstance().markEnd("ResourceManager.getInstance()", token);
* }
* }
*
* When instrumentattion is completed, a call to PerfUtil.getInstance().getSummary() will
* return the following output:
* --------------------
* Application Startup
*
* 225 : SystemManager c-tor
* 226 : SystemManager.initHandler().start
* 238 : SystemManager.initHandler().end
* 455 : SystemManager.kickOff().start
* 459 : ResourceManager.getInstance().start
* 489 : ResourceManager.getInstance().end
* 495 : SystemManager.kickOff().end
*/
public function getSummary():String
{
var testCases:Vector.<String> = new Vector.<String>();
var testCasesStart:Vector.<int> = new Vector.<int>();
var summary:String = "";
var count:int = timeVector.length;
for (var i:int = 0; i < count; i++)
{
var flag:uint = flagAt(i);
if (flag == TEST_CASE_START_ABSOLUTE || flag == TEST_CASE_START_RELATIVE)
{
// A new test case
testCasesStart.push(i);
testCases.push(idVector[i]);
}
else if (flag == TEST_CASE_END)
{
// Finished a test case
var testCaseName:String = this.idVector[i];
// Close the last matching testCaseName
for (var k:int = testCases.length - 1; k >= 0; k--)
{
if (testCases[k] == testCaseName)
{
// Print summary for the test case
summary += getSummaryFor(testCaseName, testCasesStart[k], i + 1);
break;
}
}
}
}
return summary;
}
private function flagAt(index:int):uint
{
return timeVector[index] & FLAG_MASK;
}
private function timeAt(index:int):uint
{
return timeVector[index] & TIME_MASK;
}
private function getSummaryFor(name:String, startIndex:int, endIndex:int):String
{
var timeOffset:int = (flagAt(startIndex) == TEST_CASE_START_ABSOLUTE) ? 0 : timeAt(startIndex + 1);
var summary:String = "--------------------\n" + name;
if (timeOffset > 0)
{
summary += ", time stamps relative to " + timeAt(startIndex + 1);
}
summary += "\n" + "\n";
var stampName:String;
for (var i:int = startIndex; i < endIndex; i++)
{
var flag:uint = flagAt(i);
if (flag == IGNORE)
continue;
if (flag == TEST_CASE_START_ABSOLUTE ||
flag == TEST_CASE_START_RELATIVE ||
flag == TEST_CASE_END)
{
i++;
continue;
}
var timeStamp:uint = timeAt(i);
stampName = idVector[i];
if (flag == START)
stampName += " .start";
else if (flag == END)
stampName += " .end";
summary += ((timeStamp - timeOffset).toString() + " : " + stampName + "\n");
}
return summary;
}
}
}