Figure 2   PDH.DLL Log File Formats

Log Type
Description
File Extension
Read/Write?
PDH_LOG_TYPE_PERFMON
Log file from Performance Monitor in Windows NT 4.0
LOG
Read-only
PDH_LOG_TYPE_BINARY
Log file from System Monitor in Windows 2000
BLG
Read and Write
PDH_LOG_TYPE_CSV
Log File in comma-separated value (CSV) format
CSV
Read and Write
PDH_LOG_TYPE_TSV
Log File in tab-separated value (TSV) format
TSV
Read and Write


Figure 5   DumpLog Highlights


 // FUNCTION: enum_perflog_items
 //
 // This function does the task of enumeration performance objects
 // and object items of the given log file.  Because the enum functions
 // require the machine name when enumerating on a log file, the 
 // PdhEnumMachines function is used to get a machine name that exists
 // in the give log file.
 
 void enum_perflog_items(LPCTSTR lpszLogFile)
 {
 •
 •
 •

    // enumerate machines in the log file
    dwCharsNeeded = MACHINE_LIST_SIZE;
    pdhStatus = PdhEnumMachines(lpszLogFile, mszMachines, &dwCharsNeeded);
    if (IsErrorSeverity(pdhStatus))
    {
       print_pdh_error(TEXT("PdhEnumObjects"), pdhStatus);
       return;
    }
 
    // output multistring enumeration
    lpszMachine = mszMachines;
    _tprintf(TEXT("%s contains info from the following machine names\n"),
                  lpszLogFile);
    while (*lpszMachine)
    {
       _tprintf(TEXT("%s\n"), lpszMachine);
       lpszMachine = lpszMachine + lstrlen(lpszMachine) + 1; // next string
    }
    _tprintf(TEXT("\n"));
 
    // typically you would enumerate objects/counters for each
    // machine but in this example I only enumerate them for
    // the first machine.
    lpszMachine = mszMachines;
  
    dwCharsNeeded = OBJECT_LIST_SIZE;
    pdhStatus = PdhEnumObjects(lpszLogFile, lpszMachine,
       mszObjects, &dwCharsNeeded, PERF_DETAIL_WIZARD, TRUE);
    if (IsErrorSeverity(pdhStatus))
    {
       print_pdh_error(TEXT("PdhEnumObjects"), pdhStatus);
       return;
    }
 
    // Here's a funky thing to note.
    // If lpszLogFile refers to a perfmon-type log, and
    // if the buffer is not large enough for object titles,
    // this function will succeed in return object indexes
    // This is also true for PdhEnumObjectItems.
 
    // iterate through objects and get items for each
    lptrObj = mszObjects;
    while (*lptrObj)
    {
       DWORD dwCntrCharsNeeded;
       DWORD dwInstCharsNeeded;
       LPTSTR lptrItem;
 
       dwCntrCharsNeeded = COUNTER_LIST_SIZE;
       dwInstCharsNeeded = INSTANCE_LIST_SIZE;
 
       pdhStatus = PdhEnumObjectItems(lpszLogFile,
          lpszMachine, lptrObj, mszCounters,
          &dwCntrCharsNeeded, mszInstances,
          &dwInstCharsNeeded, PERF_DETAIL_WIZARD,0);
       if (IsErrorSeverity(pdhStatus))
       {
          print_pdh_error(TEXT("PdhEnumObjectItems"), pdhStatus);
          break;
       }
       _tprintf(TEXT("Items for %s\n================\n"), lptrObj);
      
       // enumerate counter items
       _tprintf(TEXT("Counters\n"));
       
       lptrItem = mszCounters;
       while (*lptrItem)
       {
          _tprintf(TEXT("\t%s\n"), lptrItem);
          lptrItem += lstrlen(lptrItem) + 1;  // get next string
       }
      _tprintf(TEXT("\n"));
 
      // not all objects have instances 
      // dwInstCharsNeeded will be zero if no instances
       if (dwInstCharsNeeded)
       {
          lptrItem = mszInstances;
          _tprintf(TEXT("Instances\n"));
          while (*lptrItem)
          {
             _tprintf(TEXT("\t%s\n"), lptrItem);
             lptrItem += lstrlen(lptrItem) + 1;  // get next string
          }
          _tprintf(TEXT("\n\n"));
       }
       lptrObj += lstrlen(lptrObj) + 1;   // get next object
    } // while (*lptrObj)
 
    // todo: add code to display the data query range
    dwBufferSize = sizeof(PDH_TIME_INFO);
    pdhStatus = PdhGetDataSourceTimeRange(lpszLogFile, &dwNumEntries, 
                                          &pdhTimeInfo, &dwBufferSize);
    if (IsErrorSeverity(pdhStatus))
    {
       print_pdh_error(TEXT("PdhGetDataSourceTimeRange"), pdhStatus);
       return;
    }
    
    FileTimeToSystemTime((LPFILETIME)&(pdhTimeInfo.StartTime), &stStart);
    FileTimeToSystemTime((LPFILETIME)&(pdhTimeInfo.EndTime), &stEnd);
   
    _tprintf(TEXT("Time range of %s\n"), lpszLogFile);
    _tprintf(TEXT("Start Time: %2d/%2d/%4d %2d:%2d:%2d\n"),
                  stStart.wMonth, stStart.wDay, stStart.wYear,
                  stStart.wHour, stStart.wMinute, stStart.wSecond);
    _tprintf(TEXT("  End Time: %2d/%2d/%4d %2d:%2d:%2d\n"),
                  stEnd.wMonth, stEnd.wDay, stEnd.wYear,
                  stEnd.wHour, stEnd.wMinute, stEnd.wSecond);
    _tprintf(TEXT("Contains %d data samplings\n"), pdhTimeInfo.SampleCount);
 
 }
 
 // FUNCTION: perf_log_dump
 //
 // This function dumps the counter data from a log file specified by
 // the log file name, the object name, the counter name, and the optional
 // instance name.  Because the machine name is required for the
 // counter path when operating on a log file, PdhEnumMachines is used
 // to get a machine name appearing in the performance log.
 
 void perf_log_dump(LPTSTR lpszLogFile, LPTSTR lpszObj,
    LPTSTR lpszCntr, LPTSTR lpszInst){
 
 •
 •
 •

    cchMachines = MACHINE_LIST_SIZE;
    pdhStatus = PdhEnumMachines(lpszLogFile, szMachines, &cchMachines);
    if (IsErrorSeverity(pdhStatus))
    {
       print_pdh_error(TEXT("PdhEnumMachines"), pdhStatus);
       return;
    }
 
    // open a query
    pdhStatus = PdhOpenQuery(lpszLogFile, 0, &hQuery);
    if (IsErrorSeverity(pdhStatus))
    {
       print_pdh_error(TEXT("PdhOpenQuery"), pdhStatus);
       return;
    }
 
    // make the counter path
    pdhCPE.szMachineName = szMachines;  // assume first machine is in 
                                        // the path
    pdhCPE.szObjectName = lpszObj;
    pdhCPE.szInstanceName = lpszInst;
    pdhCPE.szParentInstance = NULL;
    pdhCPE.dwInstanceIndex = 0;
    pdhCPE.szCounterName = lpszCntr;
 
    dwBufferSize = PATH_BUFF_SIZE;
    pdhStatus = PdhMakeCounterPath(&pdhCPE, szPath, &dwBufferSize, 0);
    if (IsErrorSeverity(pdhStatus))
    {
       PdhCloseQuery(hQuery);
       print_pdh_error(TEXT("PdhMakeCounterPath"), pdhStatus);
       return;
    }
 
    // add the counter 
    pdhStatus = PdhAddCounter(hQuery, szPath, 0, &hCounter);
    if (IsErrorSeverity(pdhStatus))
    {
       PdhCloseQuery(hQuery);
       print_pdh_error(TEXT("PdhAddCounter"), pdhStatus);
       return;
    }
 
    dwBufferSize = 0;
    ppdhCounterInfo = NULL;
    pdhStatus = PdhGetCounterInfo(hCounter, FALSE, &dwBufferSize, 
                                  ppdhCounterInfo);
    ppdhCounterInfo = malloc(dwBufferSize);
    pdhStatus = PdhGetCounterInfo(hCounter, FALSE, &dwBufferSize, 
                                  ppdhCounterInfo);
    if (IsErrorSeverity(pdhStatus))
    {
       PdhCloseQuery(hQuery);
       print_pdh_error(TEXT("PdhGetCounterInfo"), pdhStatus);
       return;
    }
    _tprintf(TEXT("Log file: %s\n"), lpszLogFile);
    _tprintf(TEXT("Counter Path: %s\n"), ppdhCounterInfo->szFullPath); 
    _tprintf(TEXT("Machine: %s\n"), 
             ppdhCounterInfo->CounterPath.szMachineName);
    _tprintf(TEXT("Counter Data scaled at 10 to the %d power\n\n"), 
             ppdhCounterInfo->lScale);
 
    free(ppdhCounterInfo);
 
    // iterate through log file by "collecting" the query data
    pdhStatus = PdhCollectQueryData(hQuery); // reads first log record
    while (pdhStatus == ERROR_SUCCESS)
    {
       pdhStatus = PdhGetRawCounterValue(hCounter, &dwType, &pdhRawCounter);
       if (IsErrorSeverity(pdhStatus))
       {
          print_pdh_error(TEXT("PdhGetRawCounterValue"), pdhStatus);
       }
 
       FileTimeToSystemTime((LPFILETIME)&(pdhRawCounter.TimeStamp), &st);
 
       dwType = PDH_FMT_DOUBLE;
       pdhStatus = PdhGetFormattedCounterValue(hCounter, PDH_FMT_DOUBLE,
          &dwType, &pdhFmtCounter);
       if (IsErrorSeverity(pdhStatus))
       {
          print_pdh_error(TEXT("PdhGetFormattedCounterValue"), pdhStatus);
       }
 
       _tprintf(TEXT("Time Stamp: %02d:%02d:%02d    "), st.wHour, 
          st.wMinute, st.wSecond);
       _tprintf(TEXT("Value: %0.4lf\n"), pdhFmtCounter.doubleValue);
 
       pdhStatus = PdhCollectQueryData(hQuery);  // reads next log record
    } //    while (pdhStatus == ERROR_SUCCESS)
 
    PdhCloseQuery(hQuery);
 }

Figure 6   CvtLog Excerpts


 •
 •
 •
 // FUNCTION: _tmain
 //
 // The main routine contains the bulk of the algorithm of transferring
 // data from one log file and placing it into another performance log.
 // 
 // The procedure for doing so is
 //    open source log using a query
 //    add counters to the query
 //    open the destination log file and refer to the query
 //    for each data sample in the source log
 //        read a data sample and write it to the output log
 //    close both log files.
 
 void __cdecl _tmain(int argc, TCHAR **argv)
 {
 •
 •
 •

    pdhStatus = PdhOpenQuery(lpszInfile, 0, &hQuery);
    if (IsErrorSeverity(pdhStatus))
    {
       print_pdh_error(TEXT("PdhOpenQuery"), pdhStatus);
       return;
    }
 •
 •
 •

    pdhStatus = PdhBrowseCounters (&BrowseInfo);
    if (IsErrorSeverity(pdhStatus))
    {
       PdhCloseQuery(hQuery);
       print_pdh_error(TEXT("PdhBrowseCounters"), pdhStatus);
       return;
    }
 
    pdhStatus = PdhOpenLog(
       lpszOutfile, PDH_LOG_WRITE_ACCESS | PDH_LOG_CREATE_ALWAYS,
       &dwOutFileType, hQuery,  // links the input file to output file
       0, NULL,                 // no user caption
       &hLog);
    if (IsErrorSeverity(pdhStatus))
    {
       PdhCloseQuery(hQuery);
       print_pdh_error(TEXT("PdhOpenLog"), pdhStatus);
       return;
    }
 
    // now do the transfer from the hQuery to the hLog
    // this will loop until all datapoint from the
    // input log file is exhausted
    // Typically, you can call PdhUpdateLog until
    // the all of the records of the input log have
    // been read.  But in my experience PdhUpdateLog
    // may fail for the first record or two on some
    // perfmon logs.  So instead we can get the 
    // number of data samplings and use a for loop
    // Get the time range of the input log data.
    // This also retrieves the number sample count
    // which is the number of records in the log file
    dwBuffSize = sizeof(PDH_TIME_INFO);
    pdhStatus = PdhGetDataSourceTimeRange(lpszInfile,
        &dwNumEntries, &pdhTimeRange, &dwBuffSize);
 
    // PdhUpdateLog will get the data from the hQuery
    // and write it to the output log
    for (i=0; i<pdhTimeRange.SampleCount; i++)
       pdhStatus = PdhUpdateLog(hLog, NULL);
 
    pdhStatus = PdhUpdateLogFileCatalog(hLog);
    if (IsErrorSeverity(pdhStatus))
    {
       print_pdh_error(TEXT("PdhUpdateLogFileCatalog"), pdhStatus);
    }
    
    PdhCloseLog(hLog, 0);
    PdhCloseQuery(hQuery);
 }
 // FUNCTION: pdhBrowseCallback
 //
 // When the user clicks the Add button on the browse counters dialog
 // presented by the PdhBrowseCounters function, this function is called.
 // The browse counters dialog fills in the szCounterBuffer buffer (locally
 // declared in main) with a path of the counter selected by the user.
 // This function has a reference to this buffer, the query handle, and
 // other information for counter set bookkeeping by a pointer to a
 // structure (PDH_BROWSE_COUNTER_ARGS) I define in the beginning of
 // this source.
 
 PDH_STATUS WINAPI pdhBrowseCallback(DWORD dwArg)
 {
 •
 •
 •
   pdhArgs = (PPDH_BROWSE_COUNTER_ARGS)dwArg;
 
    hQuery = pdhArgs->hQuery;
    ahCounters = pdhArgs->ahCounters;
    i = pdhArgs->dwIndexCount;     // current location in ahCounters array
 
    // lpszCounterPath may be a multistring
    // this works for one or many paths passed to this function
    lptrPath = pdhArgs->lpszCounterPath;
    while(*lptrPath)
    {
       if (i >= pdhArgs->dwMaxCounters)
       {
          _tprintf(TEXT("Counter Handle Array Limit (%d) Reached\n"), 
             pdhArgs->dwMaxCounters);
          return ERROR_SUCCESS;
          break;
       }
       _tprintf(TEXT("adding counter  --  %s\n"), lptrPath);
 
       pdhStatus = PdhAddCounter(hQuery, lptrPath, 0, &(ahCounters[i]));
       if (ERROR_SUCCESS != pdhStatus)
       {
          print_pdh_error(TEXT("PdhAddCounter"), pdhStatus);
          break;
       }
 
       lptrPath += lstrlen(lptrPath) + 1;
       i++;
    } 
    pdhArgs->dwIndexCount = i;  // update new location in ahCounters array
    return ERROR_SUCCESS; // means give control back to the UI
 }