views:

155

answers:

4

I am writing a C# application that needs to be able to tell how much time it takes for a certain application to open. I am using the Stopwatch class as my timer. Start time is easy since I set it exactly with the call to run the .exe. The problem is finding out how to time when the program is done opening. The only thing I could think of to test this is using a PerformanceCounter object and checking when CPU% is less than a certain number using a while loop.

At the moment I am using PerformanceCounter, but I am not having luck with it displaying the CPU% (it's always displaying 0). My guess for this is either that the application opens up faster than the PerformanceCounter can check for the CPU% or that the PerformanceCounter is not seeing the process name because I'm calling it too quickly (I highly doubt the latter due to the fact that I think I would get errors if this happened).

Are there any other ways to have this problem solved? Is there something I might be doing wrong that is giving me a 0 CPU% all the time? I am not looking for external tools outside of my application. Here is a sample of my code:

otherApp = new otherApplication.Application();
PerformanceCounter cpuCounter = new PerformanceCounter("Process",
"% Processor Time", "otherApplication");
//This next line is to check if PerformanceCounter object is working
//MessageBox.Show(cpuCounter.NextValue().ToString());
stopwatch.Start();
while (cpuCounter.NextValue() > 10)
{
}
stopwatch.Stop();

Edited: Changed code to say otherApp and otherApplication instead of myApp and myApplication, so that it may be easier to understand.

+2  A: 

You may be better off using a known location in your code (a particular method that's called when all initialization work is complete) rather than relying on CPU utilization as a heuristic.

LBushkin
I'm looking into this too. Since I'm using a call to create an instance of an application via ActiveX, I might be able to use a simple command that only can be processed by the application once it's loaded. That should give me as close to a "application startup time" as I can get.
Brundle
I ended up using a property from the other application after the "new" call. Then I stopped the timer. That way I knew that the creation was completely finished. Yes, there may be some slight extra time in there due to the property call, but this will not affect MY ultimate goal.
Brundle
+1  A: 

If you own the code for both your application and the application you are starting, you can use System.Threading.Mutex to communicate between myApplication and otherApplication. This way, otherApplication can tell you when it has finished initializing.

davisoa
+2  A: 

If your application is a window application, i.e. if it is a process with a message loop then the standard way would be to use the Process.WaitForInputIdle method.

This method will block until the respective process has reached the idle state for the first time. This is the state when the main window of the application is created and it is okay to send messages to the application1.

The name of the method is a little bit confusing, it should really be called WaitForProcessStartupComplete.

using System;
using System.Diagnostics;

class StartupWatch
{
    static void Main()
    {
        string application = "calc.exe";
        Stopwatch sw = Stopwatch.StartNew();
        Process process = Process.Start(application);
        process.WaitForInputIdle();
        Console.WriteLine("Time to start {0}: {1}", application, sw.Elapsed);
    }
}

1Note that there might be further initialization going on in a background thread until the application is completely ready. However, being able to handle window messages is probably the clearest definition of an application being completely started.

Update:

If you need to measure the start-up time of a COM server you can still use Process.Start and then use AccessibleWindowFromObject to access the actual COM object for automation. The procedure is a bit complicated and you will need to know the window class name of the accessible object.

Below is a sample how you can measure the start-up time of Word and get a Word.Application object at the same time, see the comments how you would have to adjust it to suit your COM server.

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Word = Microsoft.Office.Interop.Word;

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00020400-0000-0000-C000-000000000046")]
public interface IDispatch
{
}

class StartupWatch
{
    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("Oleacc.dll")]
    static extern int AccessibleObjectFromWindow(IntPtr hwnd, uint dwObjectID, byte[] riid, out IDispatch ptr);

    public delegate bool EnumChildCallback(IntPtr hwnd, ref IntPtr lParam);

    [DllImport("User32.dll")]
    public static extern bool EnumChildWindows(IntPtr hWndParent, EnumChildCallback lpEnumFunc, ref IntPtr lParam);

    [DllImport("User32.dll")]
    public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

    public static bool EnumChildProc(IntPtr hwndChild, ref IntPtr lParam)
    {
        StringBuilder buf = new StringBuilder(128);
        GetClassName(hwndChild, buf, 128);
        if (buf.ToString() == "_WwG")
        {
            lParam = hwndChild;
            return false;
        }
        return true;
    }

    static Word.Application GetWordApplicationObject(Process process)
    {
        Word.Application wordApp = null;
        if (process.MainWindowHandle != IntPtr.Zero)
        {
            IntPtr hwndChild = IntPtr.Zero;

            // Search the accessible child window (it has class name "_WwG") 
            // as described in http://msdn.microsoft.com/en-us/library/dd317978%28VS.85%29.aspx
            //
            // adjust this class name inside EnumChildProc accordingly if you are 
            // creating another COM server than Word
            //
            EnumChildCallback cb = new EnumChildCallback(EnumChildProc);
            EnumChildWindows(process.MainWindowHandle, cb, ref hwndChild);

            if (hwndChild != IntPtr.Zero)
            {
                // We call AccessibleObjectFromWindow, passing the constant OBJID_NATIVEOM (defined in winuser.h) 
                // and IID_IDispatch - we want an IDispatch pointer into the native object model.
                //
                const uint OBJID_NATIVEOM = 0xFFFFFFF0;
                Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
                IDispatch ptr;

                int hr = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), out ptr);
                if (hr >= 0)
                {
                    // possibly adjust the name of the property containing the COM  
                    // object accordingly
                    // 
                    wordApp = (Word.Application)ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);
                }
            }
        }
        return wordApp;
    }

    static void Main(string[] args)
    {
        Stopwatch sw = Stopwatch.StartNew();
        Process process = Process.Start(@"C:\Program Files (x86)\Microsoft Office\Office12\WINWORD.EXE");
        process.WaitForInputIdle();
        Console.WriteLine("Time to start {0}: {1}", "Word", sw.Elapsed);
        Word.Application wordApp = GetWordApplicationObject(process);
        Console.WriteLine(string.Format("Word version is: {0}", wordApp.Version));
    }
}
0xA3
I'm looking into this at the moment. I am actually using an ActiveX object to create the instance of the application, so I don't think Process.Start would work for me. But if there is some way to use the Process class with a process name from Windows Task Manager to do this, that would be interesting.
Brundle
@Brundle: Using `Process.GetProcessById(int processId)` or `Process.GetProcessesByName(string processName)` you can get hold of a process using the information that you see in Task Manager. However, this will be of no use if you want to measure startup performance, simply because the application already needs to be started to appear in Task Manager.
0xA3
@0xA3: I just tried Process.GetProcessesByName(myApplication) with process[0].WaitForInputIdle() and it seems to be working. Although I got similar times when I just took those two lines of code out, so it is very hard to tell.
Brundle
@Brundle: Regarding your last comment: Such a measurement does not yield any meaningful result. You must start the process in the very same code and not retrieve an already started instance. That's probably why you see no difference.
0xA3
+1  A: 

This is a classic Heisenberg problem, you affect the outcome of the test by your measurement.

The startup time of a managed app is normally dominated by the cold-start time. The time needed by the file system to find all the assemblies. As opposed to warm-start time, a much smaller number you see when the assemblies are present in the file system cache. You then only see the amount of time the JIT compiler needs to compile the startup code of your program. Your test will always be a warm-start because running the test code puts the .NET assemblies in the file system cache. Happy numbers, to be sure, but not accurate.

You will have to write your test in unmanaged code. Some scripting language that you're familiar with. Help it along by calling Application.Exit() in the Shown event of the main form so that your program immediately quits when the first form is visible. You now only have to measure how long the process was executing. Could be as simple as .bat file that looks like this:

time /t
start /wait yourapp.exe
time /t

Reboot the machine before you run the test so you can be sure that Prof. Heisenberg isn't around. And keep in mind that the exact number you measure won't likely repro on your customer's machine. Because it is so dependent on the state of the hard disk. Only use it for measuring the success of incremental improvements to your code.

Hans Passant
Good points, but from the OP's sample it might also be the case that `otherApplication.Application()` refers to an external COM server such as Word or Excel.
0xA3
@0xA3 - true, had not considered that. Not much point in measuring that though, can't make Office programs quicker.
Hans Passant