65.9K
CodeProject 正在变化。 阅读更多。
Home

性能计数器简介

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (99投票s)

2004年10月18日

11分钟阅读

viewsIcon

853201

downloadIcon

10259

介绍应用程序性能监控。

引言

尽管 .NET Framework 在 System.Diagnostics 命名空间中提供了对性能计数器的使用,但出于某种原因,关于如何使用性能计数器来计算平均执行时间的文档却很少。当我开始使用 PerformanceCounterType.AverageTimer32 时,性能监视器并没有显示预期的结果。

我开始深入研究 CodeProject 和一些新闻组,发现许多文章都涵盖了同样的问题。在我找到解决方案后,我开始写一篇文章,介绍如何在 .NET 中使用性能计数器,特别是如何使用 AverageTimer32 性能计数器。所以,文章就在这里了!

抱歉,我只有德语版的 Windows,因此文章中的图片包含德语的按钮名称等。我尝试找到正确的英文翻译,但不太确定它们是否准确。也许有人能提供英文版的图片,以便我在这里提供。

入门

性能计数器可以监视系统组件,如处理器、内存和网络 I/O。如果您在应用程序中使用性能计数器,它们可以发布与性能相关的数据,以便与可接受的标准进行比较。性能计数器在 Microsoft 操作系统 Windows 2000 及更高版本中可用。

可以通过性能监视器 (perfmon.exe) 使性能计数器可见。稍后,我将展示如何在性能监视器中添加和删除性能计数器。

性能计数器类型

有各种不同的性能计数器类型,涵盖不同的性能关注点。它们从计数器到计算平均值的计数器都有。有些性能计数器类型仅用于特殊情况,但以下列表包含您通常会使用的大多数常见类型 - 本文将涵盖这些内容。

  • NumberOfItems32 - 一个计数器,用于计数项目数量。您可以使用它来计算操作执行了多少次,或者计算您处理的总项目数。
  • RateOfCountsPerSecond32 - 一个计数器,用于跟踪每秒的项目或操作数量。
  • AverageTimer32 - 一个计数器,用于测量执行操作所需的平均时间。该计数器通过在特定时间内完成的项目数量除以总经过时间来计算。这与 ... 相结合。
  • AverageBase - AverageTimer32 的基本计数器,用于计数在经过的时间内完成的项目数量。

您将在 System.Diagnostics.PerformanceCounterType 枚举中找到性能计数器类型。该枚举中的某些计数器以“Base”结尾,这表明它们支持执行计算的其他计数器(如 AverageTimer32)。每当您设置一个执行计算的计数器时,您都需要设置一个支持的“Base”计数器。

类别、计数器和实例

性能计数器被组合在类别下,例如 ProcessorMemory。一个类别将性能计数器归类到一个逻辑单元。性能计数器本身可以细分为实例,例如进程、线程或物理单元。

示例

在监视进程执行时占用的处理器负载时,您会在 Processor 类别中找到 % Processor time 性能计数器。在该计数器下,您会找到当前在您系统上运行的每个进程的一个实例。

性能计数器的创建和设置

有两种方法可以创建性能计数器。您可以使用 Server-Explorer 创建它们,或者通过代码以编程方式创建它们。使用 Server-Explorer 创建性能计数器比通过代码更容易,但在生产机器上,您可能无法安装 Visual Studio .NET 来利用此功能。但是,我将解释这两种方法。

使用 Server-Explorer 创建性能计数器

设置性能类别和计数器最简单的方法是使用集成在 Visual Studio .NET 中的 Server-Explorer。通常,您会在左侧工具栏中找到它,在那里您还可以找到用于设计 Windows 窗体的工具箱。如果您看不到它,可能是您之前将其关闭了。然后,转到“视图”菜单并选择“Server-Explorer”选项。

设置新类别非常容易。打开“Server-Explorer”并展开“Performance counters”树节点。树应该会打开,您应该会看到当前在您的系统上可用的所有性能类别(图 1)。

图 1

现在右键单击“Performance counters”并选择“Add new category”来添加一个新类别。输入名称、描述,并添加一些计数器,然后就完成了。您还可以右键单击一个类别并选择“Show category”来为任何现有类别添加更多计数器(图 2)。

图 2

通过代码安装性能计数器

性能计数器可以在运行时通过代码手动创建。这需要更多的努力,但一旦您学会了如何做,它就像通过 Server-Explorer 一样简单。在通过代码创建性能计数器和/或类别时,您必须注意,运行代码的用户必须具有适当的管理权限。在使用 Web 应用程序中的性能计数器时,这可能会成为一个问题,因为 ASP.NET 用户没有这些权限。

以编程方式安装性能计数器包括使用 System.Diagnostics 命名空间中的一些类。

  • PerformanceCounterCategory - 对性能类别进行操作(创建、删除、存在)。
  • CounterCreationDataCollection - 一个集合类,用于为类别创建计数器。
  • CounterCreationData - 一个类,您可以在其中定义性能计数器的形状(名称、描述和类型)。

要创建一个新的性能类别并附带一些计数器,您首先需要创建一个 System.Diagnostics.CounterCreationDataCollection 来保存您想要创建的计数器的信息。然后,为每个计数器创建一个 System.Diagnostics.CounterCreationData 实例,并将其添加到集合中。使用 System.Diagnostics.PerformanceCategory.Create 方法来创建类别以及集合中存储的所有相关计数器。

让我们看一些代码

if (!PerformanceCounterCategory.Exists("MyCategory"))
{
    CounterCreationDataCollection counters = new CounterCreationDataCollection();

    // 1. counter for counting totals: PerformanceCounterType.NumberOfItems32
    CounterCreationData totalOps = new CounterCreationData();
    totalOps.CounterName = "# operations executed";
    totalOps.CounterHelp = "Total number of operations executed";
    totalOps.CounterType = PerformanceCounterType.NumberOfItems32;
    counters.Add(totalOps);

    // 2. counter for counting operations per second:
    //        PerformanceCounterType.RateOfCountsPerSecond32
    CounterCreationData opsPerSecond = new CounterCreationData();
    opsPerSecond.CounterName = "# operations / sec";
    opsPerSecond.CounterHelp = "Number of operations executed per second";
    opsPerSecond.CounterType = PerformanceCounterType.RateOfCountsPerSecond32;
    counters.Add(opsPerSecond);

    // 3. counter for counting average time per operation:
    //                 PerformanceCounterType.AverageTimer32
    CounterCreationData avgDuration = new CounterCreationData();
    avgDuration.CounterName = "average time per operation";
    avgDuration.CounterHelp = "Average duration per operation execution";
    avgDuration.CounterType = PerformanceCounterType.AverageTimer32;
    counters.Add(avgDuration);

    // 4. base counter for counting average time
    //         per operation: PerformanceCounterType.AverageBase
    CounterCreationData avgDurationBase = new CounterCreationData();
    avgDurationBase.CounterName = "average time per operation base";
    avgDurationBase.CounterHelp = "Average duration per operation execution base";
    avgDurationBase.CounterType = PerformanceCounterType.AverageBase;
    counters.Add(avgDurationBase);


    // create new category with the counters above
    PerformanceCounterCategory.Create("MyCategory", 
            "Sample category for Codeproject", counters);
}

执行上面的代码会创建一个名为“MyCategory”的新性能类别,并向其中添加一些性能计数器(“# operations executed”、“# operations / sec”、“average time per operation”)。但是您还不能更改计数器的值。打开 Server-Explorer 并切换到“Performance counters”。您会在那里找到新类别,以及该类别下的三个新计数器。可能需要通过右键单击“Performance counters”并选择“refresh”来刷新视图,以使更改可见(图 3)。

图 3

正如我之前所说,创建需要计算的计数器需要一个支持的基本计数器。上面的示例仅向类别添加了三个计数器,但向 CounterCreationDataCollection 添加了四个 CounterCreationData 实例。如果您仔细查看,您会发现第三个实例的类型是 System.Diagnostics.PerformanceCounterType.AverageTimer32 (avgDuration),最后一个实例的类型是 System.Diagnostics.PerformanceCounterType.AverageBase (avgDurationBase)。avgDurationBaseavgDuration 的支持计数器。稍后监视性能时,您将看到这两个计数器如何协同工作。重要的是要知道,支持计数器 **必须始终跟随** 要监视性能的计数器!

要创建新类别并添加一些性能计数器,您必须

  • 检查类别是否已存在 (PerformanceCounterCategory.Exists())。
  • 创建一个 CounterCreationDataCollection 并添加一些 CounterCreationData
  • 创建类别 (PerformanceCategory.Create())。

请记住,您将无法创建已存在的类别和/或计数器!

使用性能计数器

安装性能计数器后,您通常希望使用它们来监视性能。为此,您可以使用 **System.Diagnostics.PerformanceCounter** 类。System.Diagnostics.CounterCreationDataSystem.Diagnostics.PerformanceCounter 之间的区别在于,System.Diagnostics.CounterCreationData 实际上将性能计数器添加到本地机器上的一个类别中,而 System.Diagnostics.PerformanceCounter 用于创建性能计数器的实例并更改性能值。

创建用于工作的性能计数器

创建用于工作的性能计数器很简单。创建一个 System.Diagnostics.PerformanceCounter 的新实例,并正确设置以下属性。

  • CategoryName - 计数器所属类别的名称。
  • CounterName - 您要创建的计数器的名称。
  • MachineName - 计数器运行的机器名称(“.” 表示本地机器)。
  • ReadOnly - 指示您是否只能读取计数器;由于我们想写入性能数据,因此将其标记为 false

您可以将其他属性保留为默认值,因为我们现在不需要更改它们。

// create counters to work with
_TotalOperations = new PerformanceCounter();
_TotalOperations.CategoryName = "MyCategory";
_TotalOperations.CounterName = "# operations executed";
_TotalOperations.MachineName = ".";
_TotalOperations.ReadOnly = false;

_OperationsPerSecond = new PerformanceCounter();
_OperationsPerSecond.CategoryName = "MyCategory";
_OperationsPerSecond.CounterName = "# operations / sec";
_OperationsPerSecond.MachineName = ".";
_OperationsPerSecond.ReadOnly = false;

_AverageDuration = new PerformanceCounter();
_AverageDuration.CategoryName = "MyCategory";
_AverageDuration.CounterName = "average time per operation";
_AverageDuration.MachineName = ".";
_AverageDuration.ReadOnly = false;

_AverageDurationBase = new PerformanceCounter();
_AverageDurationBase.CategoryName = "MyCategory";
_AverageDurationBase.CounterName = "average time per operation base";
_AverageDurationBase.MachineName = ".";
_AverageDurationBase.ReadOnly = false;

更改性能计数器值

监视性能意味着在一定时间后更改性能值。为此,我们可以在 System.Diagnostics.PerformanceCounter 实例上调用以下方法之一。

  • Increment() - 将计数器的值增加 1。
  • IncrementBy() - 将计数器的值增加指定的值。
  • Decrement() - 将计数器的值减少 1。
  • DecrementBy() - 将计数器的值减少指定的值。
  • RawValue - 属性将计数器的值设置为指定值。
// simply increment the counters
_TotalOperations.Increment();
_OperationsPerSecond.Increment();
// increment the timer by the time cost of the operation
_AverageDuration.IncrementBy(ticks);
// increment base counter only by 1
_AverageDurationBase.Increment();

使用 AverageTimer32 和 AverageBase

在上面的示例代码中,您可以看到我们必须增加用于计算平均值的计数器。类型为 PerformanceCounterType.AverageTimer32 的计数器会根据两次调用之间经过的时间来增加,而类型为 PerformanceCounterType.AverageBase 的基本计数器会为每个操作增加 1。

PerformanceCounterType.AverageTimer32 的 .NET 文档指出,用于计算计数器值的公式为 ((N2 - N1)/F)/(B2 - B1),其中 N1N2 是操作开始和结束的时间戳,B1B2 是基本值,F 定义为“滴答频率”,它被除以时间跨度,“以便结果可以秒为单位显示”。

我们应该期望可以使用 System.DateTime.Now.Ticks 来测量 N1N2,但事实并非如此,因为这些值略有不准确。实际上,我们应该通过 Interop 使用 QueryPerformanceCounter() 方法。您可以自行决定,这更像是框架中的一个 bug,而不是文档的不足。

[DllImport("Kernel32.dll")]
public static extern void QueryPerformanceCounter(ref long ticks);

// [...]

PerformanceCounterSample test = new PerformanceCounterSample();
Random rand = new Random();
long startTime = 0;
long endTime = 0;

for (int i=0; i<1000; i++)
{
    // measure starting time
    QueryPerformanceCounter(ref startTime);

    System.Threading.Thread.Sleep(rand.Next(500));

    // measure ending time
    QueryPerformanceCounter(ref endTime);

    // do some processing
    test.DoSomeProcessing(endTime - startTime);
}

使性能可见

你们中的大多数人已经知道如何设置性能监视器来使性能计数器可见。但对于所有不知道的人,我将用几句话来解释。

您可以在“控制面板”的“管理工具”下找到性能监视器。双击它,会弹出一个窗口,Y 轴范围从 0 到 100,X 轴是虚构的,代表经过的时间。您可以使用 按钮添加计数器。在打开的窗口中,应该预先选择 Processor 类别,并在该类别中选择 Processor time 计数器。您可以使用下拉列表框更改类别,并使用其下方的列表框更改计数器。在右侧,您可以看到可用的实例。如果您使用默认值,并且您有一个多处理器机器,您可以看到与您拥有的处理器数量相同的实例。每个处理器一个实例,以及一个名为 _Total 的实例,表示所有处理器使用的总处理器时间。

如果您的机器是单处理器运行的,请将类别切换到 Process,然后观察包含计数器的列表框的变化。同样,processor time 应该被预先选中,但现在您应该看到当前运行的每个进程的实例。选择 Idle 进程并单击 Add,然后关闭窗口。现在您可以看到性能监视器开始记录性能。稍微移动鼠标,为 Idle 进程分配一些处理器时间,您应该会看到绘制的曲线在您移动鼠标时向下移动,并在您保持静止时再次上升到 100(如果您没有其他耗时进程在运行)。

图 4

出于某些原因,您可能需要更改计数器的比例。为此,在性能监视器中任意位置右键单击,然后选择 Properties,然后选择 Data。选择您需要更改比例的计数器,然后使用下拉列表框更改因子,然后单击 OK。您还可以在此处更改为该计数器绘制的曲线的颜色和粗细。

尝试使用性能监视器来了解如何使用它来监视性能。

整合

以下示例设置了一个新类别“MyCategory”,并向其中添加了一些性能计数器(“# operations executed”、“# operations / sec”、“average time per operation”)。

它通过在经过一些随机时间后调用方法 PerformanceCounterSample.DoSomeProcessing() 来模拟处理,并更新计数器。要查看性能流,请打开性能监视器并添加相应的计数器。您可能需要更改“average”计数器的比例,因为平均值的计算结果是以秒为单位的,而两次操作之间的延迟总是少于 500 毫秒。

© . All rights reserved.