I recently became aware of the flash.sampler.* package, which gives you access to the same profiling data via AS3 that FlashBuilder uses for it’s profiler. It’s a very useful API for performance and memory profiling, offering enhanced introspection tools, more granular timing, method invocation counts, and method invocation sampling. It has been available in the Debug player since player 9.0r115, but I somehow remained aware of it’s existence until a few days ago.
Given that, I thought I’d write up a quick post about the API to help people get started with using it. The API is generally well documented in the Flex 4 language reference, so I’ll just focus on a few key methods and gotchas.
General information
Most of the API exists as methods within the flash.sampler package. Simply import flash.sampler.* (or the individual methods), and call the methods directly. The sampler API is only available in a debug player.
It’s also worth knowing that along with the API, the Flash player allows you to specify your own preload SWF, which will be loaded prior to other content in debug players. You can specify this in the “mm.cfg” file – the details are documented in the Flex API docs for the sampler package. This mechanism is what FlashBuilder uses to funnel sample data to the profiler. This let’s you build your own tool chain that can be invoked on any site. There’s lots of room here for building tools that run in the browser, or that connect to a companion AIR app for inspecting or profiling content.
(as an aside – jpauclair has the best write ups of mm.cfg I’ve seen – definitely worth reading if you’re interested in profiling Flash content)
Introspection
The sampler API includes a few handy introspection methods:
getSize() // returns the size in memory of the specified object in bytes
getMemberNames() // returns an iterator object containing all of the private and public members (properties / methods) on the specified object
isGetterSetter() // checks whether a proper is defined with a getter/setter |
getSize() // returns the size in memory of the specified object in bytes
getMemberNames() // returns an iterator object containing all of the private and public members (properties / methods) on the specified object
isGetterSetter() // checks whether a proper is defined with a getter/setter
Sampling
Sampling allows you to log all of the methods that were invoked over a period of time, and determine the amount of time each method took to execute. Here are the key methods:
startSampling(); // starts collecting sample data
stopSampling(); // ends a sampling session, and clears all sample data
pauseSampling(); // stops collecting sample data, without clearing existing data
getSamples(); // returns an iterator object (see below) containing all of the samples collected since the last clear
getSampleCount(); // returns the number of samples that will be returned by getSamples()
clearSamples(); // clears current samples data - you will generally want to call this after each getSamples() call to remove the samples you have already read. |
startSampling(); // starts collecting sample data
stopSampling(); // ends a sampling session, and clears all sample data
pauseSampling(); // stops collecting sample data, without clearing existing data
getSamples(); // returns an iterator object (see below) containing all of the samples collected since the last clear
getSampleCount(); // returns the number of samples that will be returned by getSamples()
clearSamples(); // clears current samples data - you will generally want to call this after each getSamples() call to remove the samples you have already read.
For the most part this API is pretty straightforward. Where it gets weird is when you look at the object returned by getSamples(). Initially, I simply traced the object, and got an error saying “Property toString not found on (null) and there is no default value”.
This made me think that I was doing something wrong, and that getSamples was returning null. Then I realized that null always traces as “null”. It occurred to me that the error was actually showing that the type of the offending object was outputting null. Running describeType on the object returns its type as “*::*”. Very interesting! Apparently it is using some low-level iterator object that doesn’t even have an AS3 type (I’m trying to get more info on this).
If you ignore the weird return type, and simply iterate the object with a for..each loop (which appears to provide a determinate order for this object type, also interesting), you will get a list of Sample objects, which includes a time stamp in microseconds, and an array of StackFrame objects which contain the function’s full path/name and the SWF line number associated with it.
var o:* = getSamples();
for each (var s:Sample in o) {
trace(s.time,s.stack);
}
clearSamples(); |
var o:* = getSamples();
for each (var s:Sample in o) {
trace(s.time,s.stack);
}
clearSamples();
Samples may also be instances of the NewObjectSample or DeleteObjectSample subclasses, which indicate the costs of instantiating or destroying an object respectively. The former has a type property that indicates the created object’s type. The latter has a size property that indicates the object’s size in memory at the time it was deleted. Both have an id property (presently undocumented on NewObjectSample) which lets you pair the create and destroy actions together.
With this information and some analysis, you can track time spent in any method (singularly or as an aggregate) and object creation.
I’ll be playing around with this API more over the next couple of weeks. I will likely talk about it a bit at FitC in Toronto and Flash and the City in NYC. I’m hoping to incorporate some of it’s capabilities into an updated version of PerformanceTest, and might build out some other tools around it. I’ll blog more as I learn more.