Windows Phone 7: Monitoring Application Startup Performance

If you’ve taken a look at the guidance for Windows Phone 7 applications you might be aware of the requirement for your application to startup within a certain period of time and for it to be responsive within another time period (I’m intentionally being vague he as I don’t want to be misquoted – you want the details, go checkout the marketplace requirements via http://developer.windowsphone.com). In addition to it being a requirement, you should always seek to minimise the startup time of your application. But during your development cycle, how do you ensure that the startup time of your application doesn’t slowly creep up? If you get to the end of the development and realise that your application takes a minute to load, you’re going to have to do a lot of work to cut this back to an acceptable level.

The answer to this question is to have a way to automatically time how long your application takes to launch. Of course you’d want to run this multiple times to get an average startup time. The Windows Phone emulator supports automation which you can use to deploy and launch your application. All you need is a way to start a timer before launching your application and then for it to stop when the application is launched.

Let’s take a console application and use that to deploy and launch our Windows Phone application. We can start a StopWatch before we launch the application but how are we going to know when to stop it? One way would be to simply stop the clock when the Launch() method returns. Unfortunately this probably won’t give us the flexibility we need to be able to record how long it takes for the application to be responsive. What we need is a way for the application itself to indicate that it has finished loading.

To solve this we add a simple WCF Service to our console application. The Windows Phone application will call this service to let it know that startup is complete. Although this will add a little overhead to the startup time itself, because we are really only interested in the regression analysis of the startup time (ie how it varies over time) this isn’t important. To make it easy to add and remove this functionality from your application  I’ve moved the service reference into a separate assembly.

The steps for measuring startup time for your application:

– Add a reference to BuiltToRoam.StartupTime.dll to your Windows Phone application
– Add the ServiceReference.ClientConfig file to your Windows Phone application which includes the BasicHttpBinding_IStartupTimeService endpoint information
– Call Startup.EndStartupTimer when your application has finished loading. For example in the Loaded event handler:

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e){
    BuiltToRoam.StartupTime.Startup.EndStartupTimer();
}

– Update the ProductId, XAPFileName and AppIconFileName settings in the app.config file of the BuiltToRoam.StartupTimeRunner console application to match your Windows Phone application.

<setting name="ProductId" serializeAs="String">
    <value>b6e9b714-2d78-4ce7-8d34-b2eb9e7b9fb7</value>
</setting>
<setting name="XAPFileName" serializeAs="String">
    <value>……TestApplicationBinDebugTestApplication.xap</value>
</setting>
<setting name="AppIconFileName" serializeAs="String">
    <value>……TestApplicationApplicationIcon.png</value>
</setting>

NB: The ProductId comes from the WMAppManifest.xml file under the Properties node in your Windows Phone application.

– Run the BuiltToRoam.StartupTimeRunner application

Output

When you run the StartupTimeRunner you should see a console window launched showing the progress of the runner. It will launch the emulator and then deploy your XAP file to it (Assuming you got the path right). Once deployed it will attempt to launch your application 20 times (this can be adjusted in the app.config file too), each time recording how long it takes to start (see image for sample output).

image

Once it has finished it will give you an average startup time. As you build out your application I’d suggest that you periodically run this to make sure you aren’t significantly lengthening the startup time.

The Code

Ok, so I guess you want to see how this works. Well I’ll leave you to go through the details but the bulk of it is in the Main method of the console application which is as follows:

static AutoResetEvent endTestRunEvent = new AutoResetEvent(false);
static void Main(string[] args){
    Guid applicationGuid = Properties.Settings.Default.ProductId;
    Console.WriteLine("Application Id: " + applicationGuid);
    string xapFile = Properties.Settings.Default.XAPFileName;
    Console.WriteLine("XAP Filename: " + xapFile);
    if (!File.Exists(Path.Combine(Environment.CurrentDirectory, xapFile))){
        Console.WriteLine("Unable to locate XAP file. Press Enter to exit");
        Console.ReadLine();
        return;
    }
    string iconFile = Properties.Settings.Default.AppIconFileName;
    Console.WriteLine("Application Icon Filename: " + iconFile);
    if (!File.Exists(Path.Combine(Environment.CurrentDirectory, iconFile))){
        Console.WriteLine("Unable to locate Icon file. Press Enter to exit");
        Console.ReadLine();
        return;
    }

    DatastoreManager dsmgrObj = new DatastoreManager(1033);
    Platform WP7SDK = dsmgrObj.GetPlatforms().Single(p => p.Name == "Windows Phone 7");

    Device device = WP7SDK.GetDevices().Single(d => d.Name == "Windows Phone 7 Emulator");

    // Use the following if you want to retrieve a reference to the actual device
    // Device device = WP7SDK.GetDevices().Single(d => d.Name == "Windows Phone 7 Device");

    using (ServiceHost host = new ServiceHost(typeof(StartupTimeService))){
        host.Open();
        Console.WriteLine("Connecting to Windows Phone 7 Emulator/Device…");
        try{
            device.Connect();
            Console.WriteLine("Windows Phone 7 Emulator/Device Connected…");

            device.InstallApplication(applicationGuid, iconFile, xapFile);

            Stopwatch sw = new Stopwatch();
            List<long> startupTimes = new List<long>();
            var exiting = false;
            for (int i = 0; i < Properties.Settings.Default.StartupIterations; i++){
                sw.Restart();
                device.LaunchApplication(applicationGuid);
                if (endTestRunEvent.WaitOne(Properties.Settings.Default.StartupTimeout * 1000)){
                    sw.Stop();
                    startupTimes.Add(sw.ElapsedMilliseconds);
                    Console.WriteLine("Run " + i + " – Startup time(ms): " + sw.ElapsedMilliseconds.ToString());
                    device.TerminateApplication(applicationGuid);   
                }
                else{
                    Console.WriteLine("Unable to startup application within specified startup timeout. Press Enter to exit.");
                    Console.ReadLine();
                    exiting = true;
                    break;
                }
            }

            if (!exiting){
                var averageStartupTime = startupTimes.Average();
                Console.WriteLine("Average startup time (ms): " + averageStartupTime);
                Console.WriteLine("Press Enter to Exit");
                Console.ReadLine();
            }
            device.CleanupApplication(applicationGuid);
        }
        finally {
            device.Disconnect();
        }
        host.Close();
    }
}

public static void ApplicationStarted(){
    endTestRunEvent.Set();
}

Download Source Code

Leave a comment