内容


使用 Microsoft 性能数据助手进行自定义分析

Comments

© Copyright International Business Machines Corporation 2003. All rights reserved.

引言

随着用户不断地努力挖掘系统在吞吐量和利用率两个方面的最大潜能,计算机应用程序的性能正变得越来越重要。然而,常常会出现这样的情况,系统性能因某些无法预料的瓶颈而受到干扰,这会降低系统上应用程序的效率,并且使系统得不到充分的利用。

在研究运行在 Windows® 平台上的应用程序的性能时,Microsoft®Windows NT® 和 Windows 2000附带的 Windows 性能监视器(Windows Performance Monitor)提供的工具使我们能够重新获得各种详细的信息,比如系统进程、CPU利用率、磁盘活动等等。这可以帮助我们确定性能瓶颈。但是这种内置功能有一个很大的缺陷,它不能自动收集这样的信息。在另一方面,Windows公开了基础性能数据库,性能监视器能够通过编程接口加以利用,而我们可以创建自定义 C++应用程序来访问这个库。

本文介绍了 Microsoft 性能数据助手库(Microsoft Performance Data Helper library,PDH.dll)及其功能,并且通过示例说明了如何利用这个库的可用性来创建有效的自定义性能监视应用程序。

本文以及文中的示例是为需要访问详细的性能数据的开发人员和测试人员准备的。

性能瓶颈

有好几种不同类型的性能瓶颈,但是事实上与所有的软件产品和应用程序都有关系的两个主要方面是 CPU和 I/O,指的就是 CPU-bound 或 I/O-bound。接近利用100%的 CPU处理能力的应用程序可认为是 CPU-bound;如果 CPU相当清闲,远远没有发挥其利用价值,而 I/O等待的时间却非常之长,那么这种应用程序可认为是I/O-bound。

让我们考虑一种特殊的场景,在这种场景中,因业务的需要必须对公司的应用程序做些改动,要求显著增加系统的吞吐量。公司试图通过把更多的用户添加到系统中来增加吞吐量,而这些增加过早地达到了峰值,其吞吐量低于所期望的水平。这样一种情形最有可能是由于性能瓶颈导致的,可能是CPU、磁盘 I/O或某些其他的方面。要达到所期望的吞吐量水平,就需要进行某些调查,以找出限制因素在哪里,接着通过分析来确定消除限制的最好方法。

例如,假定 Windows 2000 服务器系统在四路(four way)NetFinity®机器上运行 WebSphere®MQ、WebSphereBusiness Integration Message Broker 和 DB2®:

  • 系统一直在满负荷运行。
  • DataFlowEngine 进程、基本的 WebSphere Business Integration Message Broker 进程 CPU 的占用率只有7%,而不是潜在的25%(这个简单的示例涉及的是 DataFlowEngine 进程的单个实例,包含单个消息流,因此只能运行在四个 CPU 中的某一个内,相应地,最大的 CPU 占用率就是25%)。
  • 在对系统进行研究之后发现,DB2 和 WebSphere MQSeries 日志记录平均要花15毫秒。这被认为是相当高的,因而也就意味着系统可能是 I/O bound。为了解决这个问题,把系统放在快速写入高速缓存磁盘上,以减少磁盘写入时间。
  • 然后发现 DataFlowEngine 进程占用25%的处理器能力,等同于机器可用的四个处理器中的一个的能力,因而意味着,现在机器是 CPUbound,因为进程正使用它可用的所有 CPU。从某个方面来说,这实际上是非常好的,因为它意味着系统将很好地响应更快的处理器或附加的处理器(通过在某个剩余的处理器中运行 DataFlowEngine进程的另一个副本)。
  • 因此可以做出决定,运行 DataFlowEngine进程的其他副本,从而满足吞吐量目标。

虽然这是一个很小的示例,侧重的也只是两个简单的瓶颈,但是它突出了访问系统性能信息的基本优势。

那么您可以如何获取这些数据?

有两种获取数据的方式:第一种是通过 Windows操作系统附带的 Windows性能监视器。第二种是通过使用编程接口。

Windows 性能监视器

Windows 性能监视器置于 Windows操作系统之中,它允许用户访问系统性能数据。访问性能监视器的简单方法是,从Windows Start 菜单中,选择 Start => Settings => Control Panel => Administrative Tools => Performance

图1展示了最初的性能监视器窗口。

图1. 性能监视器
性能监视器
性能监视器

要开始使用性能监视器:

  1. 在工具栏上选择 Add 按钮( +)打开“Add Counters”对话框(图2)。
    图2. 性能对象及其计数器
    性能对象及其计数器
    性能对象及其计数器
  2. 指定下列值:
    • Performance object:事实上表示要监视的系统的特定区域,比如处理器、内存或进程。
    • Counter:您想要查看的实际数据项;例如,如果您选择 Process对象,那么计数器就可以是 %Processor Time、ElapsedTime、Handle Count 等等。
    • Instance:要监视的特定区域的名称(只是在某些计数器上适用);一个实例特别应用于 Process计数器,如图3所示。
    图3. 计数器及其实例
    计数器及其实例
    计数器及其实例
  3. 当您选取了所有的选项并且决定了监视什么时,就可以通过选择 ADD按钮来指示性能监视器开始监视此数据(图3)。此计数器的监视然后将立即开始,而数据将显示在监视器窗口中。
  4. 重复步骤2和3来添加其他的计数器(如果有必要的话)。当添加完所有的计数器时,选择“Add Counters”对话框上的 Close将控制权归还给性能监视器。
  5. 已收集的性能数据将显示在监视器窗口中,而且通过选择性能监视器工具栏上的相应按钮,可以采用图形、柱状图或报表等多种格式进行展示。(图4按照报表格式展示了输出。)根据这些数据,可以对应用程序和Start the系统的性能得出一个直观的结论,并且提出富有建设性的策略,就像上面的示例中所建议的那样。
    图4.性能监视器中的数据输出(按照报表的格式)
    性能监视器中的数据输出(按照报表的格式)
    性能监视器中的数据输出(按照报表的格式)

虽然性能监视器工具可用于获取性能分析所需的数据,但是也存在一些严重的缺陷:

  • 为了操作监视器面板,用户需要守在工作台上,而许多实际的系统可以在无人照管的情况下进行操作。
  • 日志记录功能可用于收集性能数据,指定用户要监视的计数器,并且把数据输出到文件中。这是一种非常重要的功能。然而,只能由日期和时间安排来触发监视,而不能由其他的系统特征或功能来触发监视。
  • 除了指定日志输出的实际格式之外(例如,逗号分隔的文件(commaseparated file,CSV)或二进制),还没有其他的选项可用来自定义输出,以适应特定用户的独特需要。

消除这些限制的实际选项将引出更合乎需要的解决方案。

自定义应用程序

Windows性能监视器所用的编程接口是以性能数据助手库(PDH.dll)及一系列 C/C++头文件(即 pdh.hpdhmsg.hwinperf.h ,它们是操作系统附带的)的形式公开给用户的。这种库的可用性使用户能够用 C/C++编写自定义的应用程序来连接到系统性能计数器并从中收集数据,然后以多种可用的形式输出数据。

从性能监视器中可以得到的任何数据也可以通过自定义应用程序得到。利用编写良好的自定义性能监视应用程序能够完全解决上面所列的性能监视器存在的问题:计数器可以在任何时候从现有的自动化组件中除去,可以在规定的时间内运行,在退出时把结果存放在可读取的文件中以供日后分析之用。

要创建和使用有效的性能监视应用程序:

  1. 决定您想要监视哪些计数器(即进程等)。
  2. 决定您想要把这些计数器应用于哪些实例(如果合适的话)。
  3. 决定应用程序设计问题,比如监视持续时间和监视间隔时间。
  4. 编写代码来进行监视,包括以下几个基本步骤:
  5. 运行应用程序。
  6. 分析数据。

决定需要监视哪些计数器和把它们应用于哪些实例常常是非常困难的。在最近发生的一个事例中,这一点体现得尤其明显。测试人员在测试运行的过程中对系统的性能进行研究,却发现收集的数据不够,难以详细分析正在运行的应用程序。经过仔细考虑后认为,不仅仅需要先前收集的系统数据,而且还需要进程级性能数据。通过在“AddCounters”对话框中选择 Process 性能对象,就可以显示进程级计数器的整个清单(图5)。

图5. 在选择 Process 性能对象时可用的计数器
在选择 Process 性能对象时可用的计数器
在选择 Process 性能对象时可用的计数器

在这个事例中,测试人员最初认为他们需要监视的是CPU、内存和 I/O 进程。然而,在检查了计数器说明之后,他们断定最合乎他们需要的计数器将是:

  • % Processor Time
  • Working Set
  • I/O Write Bytes/sec.

教训就是:如果您不能确定一个特定的计数器数据什么样的数据,就请高亮显示该计数器,然后选择 EXPLAIN来显示描述(图6)。这种信息可以帮助您决定可用的计数器中哪一种将满足您的需求。

图6. 带有对计数器的描述的 Explain Text 窗口
带有对计数器的描述的 Explain Text 窗口
带有对计数器的描述的 Explain Text 窗口

与每个计数器有关的各个实例的作用通常都是自解释的,这可以帮助决定度量组件。

需要根据所作的计数器和进程决定来进行下面的决策:

  • 监视的持续时间。
  • 应该间隔多少时间进行实际的度量。
  • 如何指定要监视哪些计数器(硬编码到应用程序中或者从文件中动态读取)。

这些元素只是在监视特定解决方案的需求时才有的,它们对于创建成功的应用程序至关重要。下一部分将说明如何用自定义编码来表示这些元素。

编码示例

这一部分包含用 C编码的几个示例,它们将说明构建有效的自定义性能监视应用程序所需的许多关键要素和函数。

开始时,在访问所需功能的自定义应用程序的开头包含若干头文件是必要的:

#include <windows.h>, #include <pdh.h>, #include <PDHMSG.H>, #include <WINPERF.H>

现在,让我们看一看在使用 PDH获取性能数据时需要包括的 自定义程序的四个基本步骤中的每一个。下面将详细讨论每个部分以及将要执行每项任务的功能查询。

1. 创建查询

查询是计数器的集合,它是用自定义代码创建的,用于管理性能数据的集合。查询用在 PDH函数调用中更新它管理的计数器,因而获取性能数据。创建查询会返回一个句柄,可以用它来访问 PDH函数中的查询。

PdhOpenQuery:
创建一个新的查询。需要两个输入参数,返回一个参数和一个返回代码。样本用法:
if( (pdhStatus = PdhOpenQuery( pszDataSource, dwUserData, &hQuery)) == ERROR_SUCCESS)
输入参数:
pszDataSource字符串,它引用要从中读取数据的日志文件。对于实时数据捕获,请指定 NULL 值。
dwUserData与此查询相关的用户定义的值。它可以是识别此查询惟一的值,而如果没有分配值,则 C++ WORD/DWORD数据类型的 0 也将是足够的。
返回的参数:
hQuery指向已创建的查询的句柄。这个指针在随后的任何 PDH函数调用中都是必需的。

如果成功地开始了此查询,那么它将返回值 ERROR_SUCCESS ;否则将返回错误的代码。

2. 使计数器与查询相关联
PdhAddCounter:
将计数器添加到查询。需要三个输入参数,返回一个参数和一个返回代码。样本用法:
if( (pdhStatus = PdhAddCounter( hQuery, szFullCounterPath, dwUserData, &phCounter)) != ERROR_SUCCESS)
输入参数:
hQuery指向您想将此计数器添加到的查询的句柄。
szFullCounterPath指向计数器的路径的指针。在传送此参数之前,必须首先用计数器的详细资料对其进行初始化,为了做到这一点,可以创建 PDH_COUNTER_PATH_ELEMENTS 数据结构,它包含机器、对象、计数器和实例的名称,如下所示:
PDH_COUNTER_PATH_ELEMENTS spdhCPE;
spdhCPE.szMachineName = "Some Value";
spdhCPE.szObjectName = "Some Value";
spdhCPE.szInstanceName = "Some Value";
spdhCPE.szCounterName = "Some Value"; 然后,将此数据结构传送到下面的 PdhMakeCounterPath函数调用中。
dwUserData与此查询相关的用户定义的值。它可以是识别此查询惟一的值,而如果没有分配值,则 C++ WORD/DWORD数据类型的 0 也将是足够的。
Returned Parameters:
phCounter指向已创建的查询的句柄。
PdhMakeCounterPath:
创建指向使用 PDH_COUNTER_PATH_ELEMENTS结构的成员的计数器的全路径。需要三个输入参数,返回一个参数和一个返回代码。样本用法:
pdhStatus = PdhMakeCounterPath( &spdhCPE, szFullPathBuffer, &dwpcchBufferSize, dwFlags);
输入参数:
spdhCPE指向 PDH_COUNTER_PATH_ELEMENTS 的指针。
dwpcchBufferSize如果函数调用成功,就将此参数的值设置为可用缓冲器的大小。要不然,就将它设置为所需的缓冲器的大小*。因而,此参数可以是输入参数,也可以是输出参数。

(*分析此参数的值并且相应地扩展缓冲器可能是必要的。)
dwFlags指定计数器值的格式。它可以是:
  • PDH_PATH_WBEM_RESULT:以 WMI格式返回结果。
  • PDH_PATH_WBEM_INPUT:假定输入值采用 WMI格式。
  • 0:以注册路径项的列表的形式返回结果。
返回参数:
szFullPathBuffer指向将要在 PdhAddCounter函数中使用的计数器的全路径。
dwpcchBufferSize参见上面的 dwpcchBufferSize 输入参数。

3. 收集和处理数据

收集:

既然设置了查询和添加了容器,现在就可以开始收集性能数据了。要做到这一点,可以收集原始数据并人工处理它,也可以使用内置的 PDH日志记录函数。

用于人工处理:

PdhCollectQueryData:
检索调用时在查询中指定的所有计数器的原始数据、实时数据。需要一个输入参数,返回一个返回代码。样本用法:
if( (pdhStatus = PdhCollectQueryData( hQuery)) != ERROR_SUCCESS)
输入参数:
hQuery指向您想要从中收集数据的查询的句柄。

在使用函数来收集数据时,如果不首先以某种方式对收集的数据进行处理,那么它们将会是无效的。例如,如果计数器长时间地监视某些事情(例如,每秒的 I/O数据字节),那么从 PdhCollectQueryData返回的原始值将只是数据字节的运行总数。要获得实际的每秒数据字节,您将必须接受两个样本(来获取初始值和结束值),然后根据样本之间的持续时间来进行区分。(使用 PdhGetFormattedCounterValue 函数可以为您完成这一过程。)

用于日志记录方法:

也可以如上获得相同的数据,并将其直接写入日志文件。要做到这一点,只需打开用于写入的日志,然后在每次收集数据时更新日志就行了。完成这项任务所需的两个函数是 PdhOpenLog和 PdhUpdateLog:

PdhOpenLog:
打开用于写入的日志。需要六个输入参数,返回一个参数和一个返回代码。样本用法:
pdhStatus = PdhOpenLog (szLogFileName, dwAccessFalgs, lpdwLogType, hQuery, dwMaxSize, szUserText, pdhLog)
输入参数:
szLogFileName字符串,它表示要创建的日志文件的名称/路径。
dwAccessFlags所需的对日志文件的访问级别。这些值可以是:
  • PDH_LOG_READ_ACCESS(读取)
  • PDH_LOG_WRITE_ACCESS(写入)
  • PDH_LOG_UPDATE_ACCESS(打开的用于写入的现有日志)
需要使用 OR运算符与下面的某个值组合的读取、写入和更新标志:
  • PDH_LOG_CREATE_NEW(已创建的新日志文件)
  • PDH_LOG_CREATE_ALWAYS(清除任何具有相同名称的现有日志)
  • PDH_LOG_OPEN_EXISTING(打开一个现有的日志文件,如果它不存在的话,就创建一个新的日志文件)
  • PDH_LOG_OPEN_ALWAYS(打开一个现有的日志文件或创建一个新的日志文件)
lpdwLogType要打开的日志的格式。这个值可以是:
  • PDH_LOG_TYPE_UNDEFINED(未定义的)
  • PDH_LOG_TYPE_CSV(日志有列头,后面紧跟着数据值;值是用双引号和逗号隔开的)
  • PDH_LOG_TYPE_SQL(SQL 格式的数据)
  • PDH_LOG_TYPE_TSV(日志有列头,后面紧跟着数据值;值是用双引号和标记隔开的)
  • PDH_LOG_TYPE_BINARY(二进制格式)
  • PDH_LOG_TYPE_PERFMON(性能监视器用来存储数据的只读格式;在本质上与二进制是相同的,不过缺少有效的空格方式)
hQuery指向在其上打开日志的查询的句柄。
dwMaxSize最大的日志文件大小。
szUserText用于描述日志文件的内容的字符串。
返回参数:
pdhLog指向日志文件的句柄。
PdhUpdateLog:
更新日志。需要两个输入参数,返回一个返回代码。样本用法:
pdhStatus = PdhUpdateLog (hLog, dwText))
输入参数:
hLog指向要更新的日志文件得句柄。
dwText指定要添加到日志文件的任何附加文本,比如用户想要在性能数据之外添加的注释。

一旦不再需要该日志,就应该使用 PdhCloseLog函数关闭它:

PdhCloseLog
关闭该日志。需要两个输入参数,返回一个返回代码。样本用法:
pdhStatus = PdhCloseLog (hLog, dwFlag)
输入参数:
hLog指向要关闭的日志文件的句柄。
dwFlag可以设置为 PDH_FLAGS_CLOSE_QUERY,在这种情况下,查询与日志是同时关闭的。

处理:

要处理从 PdhCollectQueryData调用检索的原始数据,就有必要调用 PdhGetFormattedDataValue。在长时间监视计数器时,这尤其重要,因为 PdhCollectQueryData函数返回的是运行的整个数据,而不是每秒的数据。

PdhGetFormattedCounterValue
检索格式化数据(formatted data)。需要两个输入参数,返回两个参数和一个返回代码。样本用法:
pdhStatus = PdhGetFormattedCounterValue( hCounter, dwFormat, &dwValue, pdhValue);此函数接受对 PdhCollectQueryData的最后一次调用返回的样本来执行它的计算。如果是长时间监视计数器,该函数就接受来自对 PdhCollectQueryData的前两次调用的数据。
输入参数:
hCounter指向将要格式化其值的的计数器的句柄。
dwFormat指定以哪一种格式返回数据:
  • PDH_FMT_DOUBLE(双精度浮点型)
  • PDH_FMT_LARGE(64位整型)
  • PDH_FMT_LONG(长整型)
返回参数:
&dwValue返回计数器的类型(比如文本或数值(可选))的指针。
pdhValue指向包含计数器值的格式化计数器数据结构的指针。

当使用日志记录方法时,可以以多种方式处理数据,这取决于指定的格式。例如,如果数据是采用 CSV格式写入的,那么就可以把日志文件导入电子表格。如何处理日志完全由用户决定。

4. 关闭查询

当查询变得多余时(因为已经收集了全部所需的数据),就可以通过调用 PdhCloseQuery函数来关闭查询。(如果是使用日志,PdhCloseLog也可以关闭查询。)

PdhCloseQuery
关闭查询。需要一个输入参数,返回一个返回代码。样本用法:
pdhStatus = PdhCloseQuery (hQuery)
输入参数:
hQuery要关闭的查询得句柄。

上面概述的函数代表了PDH库中可用的函数的样本,我们用它们说明了可以如何构造自定义应用程序。从 MicrosoftWeb站点可以获得可用函数的完整清单。

样本应用程序

下面是基于本文中所讨论的示例的样本代码。此代码示范了一个非常简单的监视应用程序,其中所有的值都是硬编码的,我们的目的只在于为您编写更复杂的应用程序提供一个良好的开端。下面的代码列出了将写入日志的最后输出。

#include <windows.h>

#include <stdio.h>

#include <tchar.h>

#include <pdh.h>

#include <pdhmsg.h>

int __cdecl _tmain (void)

{ HLOG phLog;

   PDH_STATUS          pdhStatus;

   HCOUNTER            phCounter;

   DWORD               count;

   char               szFileName[24];

   WORD               dwUserData = 0;

   HQUERY              hQuery = NULL;

   DWORD              logType = PDH_LOG_TYPE_CSV;

   CHAR                szCounterPath[45]= TEXT("\\Process(calc)\\% Processor Time");

   Strcpy              ( szFileName,"QuickMonitor.log");

// Open a query.

   (pdhStatus = PdhOpenQuery( NULL, 0, &hQuery));

// Add a counter.

   pdhStatus = PdhAddCounter( hQuery, szCounterPath,dwUserData,&phCounter);

// Open the log file for write access.

   pdhStatus = PdhOpenLog (szFileName, PDH_LOG_WRITE_ACCESS |PDH_LOG_CREATE_ALWAYS ,

   &logType, hQuery, 0, NULL, &phLog);

// Capture 10 samples and write them to the log.

   for (count = 0; count <= 10; count++) {

       pdhStatus = PdhUpdateLog (phLog, TEXT("SomeText."));

       Sleep(1000); // Sleep for 1 seconds betweensamples

   }

// Close the log and the Query

   pdhStatus = PdhCloseLog (phLog, PDH_FLAGS_CLOSE_QUERY);

   return 0;

}

在把上面的代码构建成 exe文件并且从命令行运行它之后,就创建了称为 QuickMonitor.log 的日志文件,并且将包含类似于下面这样的数据:

"(PDH-CSV 4.0) (GMT Daylight Time)(-60)","\Process(calc)\% Processor Time"

"09/07/2003 11:21:25.367","5.06732874742129e-008"

"09/07/2003 11:21:26.398","0.98039215686274506"

"09/07/2003 11:21:27.430","0"

"09/07/2003 11:21:28.461","0.97087378640776689"

"09/07/2003 11:21:29.503","3.8461538461538463"

"09/07/2003 11:21:30.554","1.9047619047619049"

"09/07/2003 11:21:31.766","0.82644628099173556"

"09/07/2003 11:21:32.808","6.7307692307692308"

"09/07/2003 11:21:33.859","6.666666666666667"

"09/07/2003 11:21:34.891","5.825242718446602"

"09/07/2003 11:21:35.922","3.8834951456310676"

这个示例中的数据展示了机器 CPU 的 0和 6.7%之间所用的计算进程,不过非常基本并且脱离了上下文。

上面的数据是以 CSV格式显示的,因此可导入电子表格,以更方便地进行分析。然而,所收集的数据最实用的格式将取决于实际问题情形的独特组合、选取的选项和执行分析的用户。

结束语

通过本文所提供的信息和编码示例,您有望初步了解自定义性能监视应用程序与性能数据助手库以其提供的可能性。这样的应用程序不仅有助于诊断性能问题,而且还可用于收集数据,以供进行概要分析和确定在系统中什么样的行为可认为是正常的行为。不管最后的结果怎样,但是如果有人想要进行自定义编码,以便收集基于Windows的应用程序的性能数据并且对其进行分析,那么本文应该为他们提供了一个良好的开端。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere
ArticleID=55384
ArticleTitle=使用 Microsoft 性能数据助手进行自定义分析
publish-date=12012003