Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get time it takes for an application to startup?

Tags:

c#

time

startup

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.

like image 627
Brundle Avatar asked Sep 09 '10 18:09

Brundle


2 Answers

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));
    }
}
like image 198
Dirk Vollmar Avatar answered Sep 23 '22 18:09

Dirk Vollmar


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.

like image 42
LBushkin Avatar answered Sep 23 '22 18:09

LBushkin