사용자 도구

사이트 도구


kb:windowsperformancecounterusingcpp

Windows Performance Counter Using C++

윈도우즈 환경에서는 PDH(Performance Data Helper) 라이브러리를 이용해, 성능 카운터 정보를 쉽게 알아낼 수 있다. 서버 프로그램에다 이 기능을 집어넣고, 통계를 파일에다 기록해두면, 나중에 뭔가 눈에 보이지 않는 문제가 생겼을 때 대처할 수 있는 근거 자료가 된다.

C++ 래퍼 구현

:!: MSDN에 보면 pdh.h 파일만 include 하면 다 되는 것처럼 되어 있으나, pdhmsg.h 파일도 포함해야 각종 상수들(PDH_XXX 시리즈)을 사용할 수 있다.

Perfmon.h
//////////////////////////////////////////////////////////////////////////////
/// \file Perfmon.h
/// \author excel96
/// \date 2005.3.28
//////////////////////////////////////////////////////////////////////////////
 
#pragma once
 
#include <string>
#include <vector>
 
//////////////////////////////////////////////////////////////////////////////
/// \class Perfmon
/// \brief 성능 모니터링을 위한 PDH(Performance Data Helper) 라이브러리 래퍼
//////////////////////////////////////////////////////////////////////////////
 
class Perfmon
{
private:
    struct IMPL;
    IMPL* m_pImpl;
 
 
public:
    /// \brief 생성자
    /// \param filename 로그 파일 이름. NULL일 경우 파일에다 기록하지 않는다.
    Perfmon(const char* filename = NULL);
 
    /// \brief 소멸자
    virtual ~Perfmon();
 
 
public:
    /// \brief 해당하는 카운터에 대한 데이터 수집을 시작한다.
    /// \param pszCounter 카운터 문자열
    /// \return bool 카운터를 무사히 추가한 경우에는 true를 반환하고, 뭔가
    /// 에러가 생긴 경우에는 false를 반환한다.
    bool start(const char* pszCounter);
 
    /// \brief 데이터 수집을 중지한다.
    void stop();
 
    /// \brief 현재 카운터의 값을 반환한다.
    /// \return long 카운터의 값
    long current() const;
 
    /// \brief 해당 경로 하위에 있는 카운터 객체 문자열들을 반환한다.
    /// \param path 경로 문자열.
    ///   - <pre>\object(parent/instance#index)\counter</pre>
    ///   - <pre>"\\LogicalDisk(*/*#*)\\*"</pre>
    ///   - <pre>"\\Network Interface(*/*#*)\\*"</pre>
    /// \return bool 카운터 객체를 무사히 얻어낸 경우에는 true, 뭔가 에러가
    /// 생긴 경우에는 false를 반환한다.
    static bool enumerate(const char* path, vector<string>& counters);
};
Perfmon.cpp
//////////////////////////////////////////////////////////////////////////////
/// \file Perfmon.cpp
/// \author excel96
/// \date 2005.3.28
//////////////////////////////////////////////////////////////////////////////
 
#include "ServerLibPCH.h"
#include "Perfmon.h"
#include <Pdh.h>
#include <PdhMsg.h>
 
#pragma comment(lib, "Pdh")
 
//////////////////////////////////////////////////////////////////////////////
/// \sturct Perfmon::IMPL
/// \brief Perfmon 클래스 내부 데이터 PIMPL
//////////////////////////////////////////////////////////////////////////////
 
struct Perfmon::IMPL
{
    HQUERY      hQuery;      ///< 쿼리 핸들
    HCOUNTER    hCounter;    ///< 카운터 핸들
    HLOG        hLog;        ///< 로그 파일 핸들
    const char* szFilename;  ///< 파일 이름
 
    /// \brief 생성자
    IMPL(const char* filename)
    : hQuery(NULL), hCounter(NULL), hLog(NULL), szFilename(filename)
    {
    }
 
    /// \brief 해당하는 카운터에 대한 데이터 수집을 시작한다.
    /// \param pszCounter 카운터 문자열
    /// \return bool 카운터를 무사히 추가한 경우에는 true를 반환하고, 뭔가
    /// 에러가 생긴 경우에는 false를 반환한다.
    bool start(const char* pszCounter)
    {
        stop();
 
        if (PdhOpenQuery(NULL, 0, &hQuery) != ERROR_SUCCESS)
        {
            filelog(NULL, "PdhOpenQuery failed - %s", GetLastErrorString().c_str());
            Assert(false && "PdhOpenQuery failed");
            return false;
        }
 
        if (PdhValidatePath(pszCounter) != ERROR_SUCCESS)
        {
            filelog(NULL, "PdhValidatePath failed - %s", GetLastErrorString().c_str());
            Assert(false && "PdhValidatePath failed");
            return false;
        }
 
        if (PdhAddCounter(hQuery, pszCounter, 0, &hCounter) != ERROR_SUCCESS)
        {
            filelog(NULL, "PdhAddCounter failed - %s", GetLastErrorString().c_str());
            stop();
            Assert(false && "PdhAddCounter failed");
            return false;
        }
 
        if (szFilename != NULL)
        {
            DWORD dwLogType = PDH_LOG_TYPE_CSV;
 
            if (PdhOpenLog(szFilename,
                PDH_LOG_WRITE_ACCESS | PDH_LOG_CREATE_ALWAYS,
                &dwLogType, hQuery, 0, NULL, &hLog) != ERROR_SUCCESS)
            {
                filelog(NULL, "PdhOpenLog failed - %s", GetLastErrorString().c_str());
                stop();
                Assert(false && "PdhOpenLog failed");
                return false;
            }
        }
 
        return true;
    }
 
    /// \brief 데이터 수집을 중지한다.
    void stop()
    {
        if (hCounter)
            PdhRemoveCounter(hCounter);
 
        if (hQuery)
            PdhCloseQuery(hQuery);
 
        if (hLog)
            PdhCloseLog(hLog, PDH_FLAGS_CLOSE_QUERY);
 
        hQuery = NULL;
        hCounter = NULL;
        hLog = NULL;
    }
 
    /// \brief 현재 카운터의 값을 반환한다.
    /// \return long 카운터의 값
    long current() const
    {
        if (PdhCollectQueryData(hQuery) != ERROR_SUCCESS)
            return 0;
 
        PDH_FMT_COUNTERVALUE value;
        if (PdhGetFormattedCounterValue(
            hCounter, PDH_FMT_LONG, NULL, &value) != ERROR_SUCCESS)
        {
            return 0;
        }
 
        if (szFilename != NULL)
            PdhUpdateLog(hLog, "This is a comment.");
 
        return value.longValue;
    }
};
 
 
//////////////////////////////////////////////////////////////////////////////
/// \brief 생성자
/// \param filename 로그 파일 이름. NULL일 경우 파일에다 기록하지 않는다.
//////////////////////////////////////////////////////////////////////////////
Perfmon::Perfmon(const char* filename)
: m_pImpl(new IMPL(filename))
{
    AssertPtr(m_pImpl);
}
 
//////////////////////////////////////////////////////////////////////////////
/// \brief 소멸자
//////////////////////////////////////////////////////////////////////////////
Perfmon::~Perfmon()
{
    stop();
    delete m_pImpl;
}
 
//////////////////////////////////////////////////////////////////////////////
/// \brief 해당하는 카운터에 대한 데이터 수집을 시작한다.
/// \param pszCounter 카운터 문자열
/// \return bool 카운터를 무사히 추가한 경우에는 true를 반환하고, 뭔가
/// 에러가 생긴 경우에는 false를 반환한다.
//////////////////////////////////////////////////////////////////////////////
bool Perfmon::start(const char* pszCounter)
{
    return m_pImpl->start(pszCounter);
}
 
//////////////////////////////////////////////////////////////////////////////
/// \brief 데이터 수집을 중지한다.
//////////////////////////////////////////////////////////////////////////////
void Perfmon::stop()
{
    m_pImpl->stop();
}
 
//////////////////////////////////////////////////////////////////////////////
/// \brief 현재 카운터의 값을 반환한다.
/// \return long 카운터의 값
//////////////////////////////////////////////////////////////////////////////
long Perfmon::current() const
{
    return m_pImpl->current();
}
 
//////////////////////////////////////////////////////////////////////////////
/// \brief 해당 경로 하위에 있는 카운터 객체 문자열들을 반환한다.
/// \param path 경로 문자열.
///   <pre>\object(parent/instance#index)\counter</pre>
///   <pre>"\\LogicalDisk(*/*#*)\\*"</pre>
///   <pre>"\\Network Interface(*/*#*)\\*"</pre>
/// \return bool 카운터 객체를 무사히 얻어낸 경우에는 true, 뭔가 에러가
/// 생긴 경우에는 false를 반환한다.
//////////////////////////////////////////////////////////////////////////////
bool Perfmon::enumerate(const char* path, vector<string>& counters)
{
    HQUERY     hQuery = NULL;
    LPSTR      szCtrPath = NULL;
    DWORD      dwCtrPathSize = 0;
    PDH_STATUS status = ERROR_SUCCESS;
    bool       bResult = false;
 
    status = ::PdhOpenQuery(NULL, 0, &hQuery);
    if (status != ERROR_SUCCESS)
        return false;
 
    // First try with an initial buffer size.
    dwCtrPathSize = 1000;
    szCtrPath = (LPSTR)::GlobalAlloc(GPTR, dwCtrPathSize);
 
    status = ::PdhExpandCounterPath(path, szCtrPath, &dwCtrPathSize);
 
    // Check for a too small buffer.
    if (status == PDH_MORE_DATA)
    {
        // dwCtrPathSize has the required length, minus the last NULL
        dwCtrPathSize++;
        ::GlobalFree(szCtrPath);
        szCtrPath = (LPSTR)::GlobalAlloc(GPTR, dwCtrPathSize);
        status = ::PdhExpandCounterPath(path, szCtrPath, &dwCtrPathSize);
    }
 
    // Upon success, print all counter path names.
    if (status == PDH_CSTATUS_VALID_DATA)
    {
        LPTSTR ptr = szCtrPath;
        char buf[512] = {0, };
        while (*ptr)
        {
            sprintf_s(buf, sizeof(buf), "%s", ptr);
            counters.push_back(string(buf));
            ptr += strlen(ptr) + 1;
        }
 
        bResult = true;
    }
 
    ::GlobalFree(szCtrPath);
    ::PdhCloseQuery(hQuery);
 
    return bResult;
}

CPU 사용량과 IOCP 쪽에서 나오는 Nonpaged Pool 사용량을 화면 및 파일에다 출력한다.

Sample.cpp
#include "Perfmon.h"
 
int main()
{
    Perfmon cpu("cpu.csv");
    Perfmon mem("mem.csv");
 
    cpu.start("\\Processor(_Total)\\% Processor Time");
    mem.start("\\Memory\\Pool Nonpaged Bytes");
 
    for (int i=0; i<10; ++i)
    {
        Sleep(1000);
        printf("CPU load: %d%, Nonpaged bytes: %d KB\n",
            cpu.current(), mem.current() / 1024);
    }
 
    return 0;
}

kb/windowsperformancecounterusingcpp.txt · 마지막으로 수정됨: 2014/11/12 11:29 저자 excel96