Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ProcessTime - A tool to get start and end time of any process

0.00/5 (No votes)
9 Dec 2004 1  
This tool uses PSAPI to list kernel and user processes. Also, it can log start and end times of user level processes.

Sample Image - ProcessTime.jpg

Introduction

ProcessTime is a simple tool that logs the processes created and terminated on your system. My colleague asked me for some type of test tool to check the end time of a Dialog application, which terminates itself after certain idle time. As a challenge, I searched the Internet but couldn't find any particular ones. So overnight I created this; hope this will be useful for some one out there. It is also very useful to catch any process that pops up in a sudden and terminates before you even notice it. It monitors user level processes and kernel level processes and lists them in two separate list controls. It displays the start time of user level processes and end time of processes that terminated only after this tool gets started. Also, it lists all the kernel processes (i.e., drivers loaded) on your system.

I don't know the way to pick up the driver loaded and unloaded time. It seems there is no way through PSAPI, but I think there are some other ways to do that. If any one knows how, please teach us. You are most welcome to modify this code or suggest to me any change.

Background

This doesn't have any complex code, it just demonstrates PSAPI basic usage. For more information about PSAPI. refer Platform SDK under MSDN or search PSAPI on MSDN. (Please don't criticize me for copying some part of code from MSDN sample ;) ) This tool uses the following APIs as basic:

  • EnumDeviceDrivers to enumerate drivers.
  • EnumProcesses to enumerate user mode processes.

How It Works

First, it gets all the processes' IDs using EnumProcesses. Then, one by one it opens the processes and adds them to the list (see code below) with all information packed inside ST_PROCESSINFO structure. This opened process will not be closed. It needs to remain open to gather end time after that process terminates. This opened process should be closed on some point, that will be on OnDestroy or whenever user presses Clear button.

void CProcessTimeDlg::AddProcessToList(DWORD processID )
{
    //

    // Adds the process name and ID to the ListCtrl

    //


    // first update the process time 

    UpdateProcessTime();

    CListCtrl *pList = (CListCtrl*)GetDlgItem(IDC_LSTPROCESS);

    int nCount = pList->GetItemCount();
    
    ST_PROCESSINFO *pstProcessInfo;
    char szBuff[MAX_PATH];
    char szProcessName[MAX_PATH] = "unknown";
    // in case EnumProcessModules fails


    char szItemString[MAX_PATH+64];

    // open the process to query the time information

    //   this handle will remain open until ClearProcessList call

    //   This should remain open to get the process terminated time 

    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, 
                                  FALSE, processID);
    if(!hProcess)
        return;
    
    for(int i=0;i<nCount; i++)
    {
        ST_PROCESSINFO *pstPrvProcessInfo = 
                       (ST_PROCESSINFO *)pList->GetItemData(i);
        
        // If the passed process id is same

        // as the already updated process in the ListCtrl

        //    then check whether it is a terminated process

        //     if not then return immediately

        //        without updating (to avoid flicker)

        if(pstPrvProcessInfo->dwProcessId == processID)
        {
            CString cszText = pList->GetItemText(i,4);
            cszText.TrimRight();

            if(cszText == "")
                return;
        }
    }
    
    HMODULE hMod;
    DWORD cbNeeded;    

    if(EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded))
        GetModuleBaseName( hProcess, hMod, szProcessName, 
                           sizeof(szProcessName));

    wsprintf(szItemString, "%u", processID);
    wsprintf(szBuff,"%d", nCount);

    // fill the structure and store the info for later updates

    pstProcessInfo        = new ST_PROCESSINFO();
    pstProcessInfo->dwProcessId    = processID;
    pstProcessInfo->hProcess    = hProcess;

    pList->InsertItem(nCount,szItemString);
    pList->SetItemText(nCount,2,szProcessName);
    pList->SetItemText(nCount,1,szBuff);

    pList->SetItemData(nCount,(DWORD)pstProcessInfo);
}

UpdateProceTime uses the API GetProcessTimes to retrieve process times, particularly start and end times. End time will not be available until the process terminates. This function can be extended to display usage time. (See David Crow's article, check the link below under "Other related articles" heading).

// this will update the process start time and end time

// (end time, only if the process has terminated)

void CProcessTimeDlg::UpdateProcessTime()
{
    CListCtrl *pList = (CListCtrl*)GetDlgItem(IDC_LSTPROCESS);
    FILETIME ftCreate, ftExit, ftKernel, ftUser;
    
    int nCount = pList->GetItemCount();

    // loop all the process in the list box

    for(int i=0;i<nCount;i++)
    {
        ST_PROCESSINFO *pstProcessInfo = 
                       (ST_PROCESSINFO *)pList->GetItemData(i);
        if(!pstProcessInfo->hProcess)
            continue;

        if(GetProcessTimes(pstProcessInfo->hProcess, 
                           &ftCreate, &ftExit, &ftKernel, &ftUser))
        {
            // Horrible, disgusting hack!

            // The two lines below basically grab the

            // contents of a FILETIME structure

            // and store it in a 64 bit integer.

            LONGLONG tUser64 = *(LONGLONG *)&ftUser;
            LONGLONG tKernel64 = *(LONGLONG *)&ftKernel;
    
            DWORD tUser, tKernel;

            // The LONGLONGs contain the time in 100 nanosecond intervals (now

            // there's a useful unit of measurement...).  Divide each of them by

            // 10000 to convert into milliseconds, and store the results in a

            // DWORD.  This means that the max time before overflowing is around

            // 4 Million seconds (about 49 days)

            tUser = (DWORD)(tUser64 / 10000);
            tKernel = (DWORD)(tKernel64 / 10000);
            
            // Format the user and kernel times, and add to the process node

            char szItem[128];

            char szFileDate[32] = { 0 };
            char szFileTime[32] = { 0 };

            if(!ftCreate.dwHighDateTime&&!ftCreate.dwLowDateTime)
            {
                strcpy(szFileDate,"");
                strcpy(szFileTime,"");
            }
            else
            {    // formatting the date & time

                GetFileDateAsString(&ftCreate, szFileDate, sizeof(szFileDate));
                GetFileTimeAsString(&ftCreate, szFileTime, sizeof(szFileTime));
            }

            wsprintf(szItem, "%s %s", szFileDate, szFileTime);

            CString cszText = pList->GetItemText(i,3);

            // if already exists then don't update, this will reduce the flicker

            if(cszText != szItem)
                pList->SetItemText(i,3,szItem);

            if(!ftExit.dwHighDateTime&&!ftExit.dwLowDateTime)
            {
                strcpy(szFileDate,"");
                strcpy(szFileTime,"");
            }
            else
            {    // formatting the date & time

                GetFileDateAsString(&ftExit, szFileDate, sizeof(szFileDate));
                GetFileTimeAsString(&ftExit, szFileTime, sizeof(szFileTime));
            }

            wsprintf(szItem, "%s %s", szFileDate, szFileTime);

            cszText = pList->GetItemText(i,4);

            // if already exists then don't update, this will reduce the flicker

            if(cszText != szItem)
                pList->SetItemText(i,4,szItem);
        }
    }
}

When this tool starts, it starts a thread, which runs behind and executes the monitoring functions in particular intervals. This makes it possible to catch any newly appearing process and end time of existing processes.

Lacking Feature

I have some more enhancements to make but due to lack of time and to maintain simplicity, I haven't done those. These are some of those...

  • Making this as service
  • Logging to a file for permanent record
  • To appear in system tray after minimize
  • Sorting

Other related articles

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here