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

一个简单的性能计数器应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.30/5 (14投票s)

2008年10月7日

CPOL

4分钟阅读

viewsIcon

133865

downloadIcon

10621

一个简单的 C# 性能计数器应用程序。

引言

长话短说,我需要监控我的工作站以及家庭网络上几台其他计算机的系统性能计数器,而且不想占用多个系统监视器 (Perfmon.exe) 实例的屏幕空间。我有一些旧计算机担任文件和打印服务器以及“开发服务器”的角色,我想随时了解当前的 CPU 负载、可用内存、网络活动等。我只想要“指示灯”,而不是“仪表盘”——一个非常精简的系统当前状态摘要,而不是花哨的图表和直方图。因此,我编写了一个 C# 应用程序来提供我所需的信息。

背景

如果您不熟悉使用性能计数器,系统监视器应用程序 (Perfmon.exe) 及其帮助文件是一个很好的起点。我在其他地方读到,用作构造函数参数的实际性能计数器名称是本地化的,因此对于非英语地区,此代码需要相应地进行更改和重新编译。

使用代码

基本上,一些 PerformanceCounter 实例在主窗体的 Form.Load 中以编程方式创建,并在 Form.Closing 中处理。整个窗体只不过是一个 StatusStrip 组件,其中每个 PerformanceCounter 的值都显示在 ToolStripStatusLabel 中,通过一个 Timer 每隔几秒更新一次。

我选择监控的性能计数器是

  • 总 CPU 处理器时间百分比(“处理器”、“% 处理器时间”)
  • 已提交内存百分比(“内存”、“% 已提交字节使用量”)
  • 页面文件使用百分比(“页面文件”、“% 使用量”)
  • 网络适配器吞吐量

监控 CPU 使用率不言而喻,根据我自己的观察,已提交内存和页面文件使用量加在一起可以让我合理地了解我的计算机的负载有多大。起初,我确实使用了其他指标,如可用内存(“内存”、“可用 MB”)和提交限制(“内存”、“提交限制”),但对我来说,这不像以百分比表示的计数器那样直观易读,通常来说,显示值越高,计算机的响应速度就越慢。这是一个个人选择,显然,在其他情况下,其他指标会更适用。

// main form's private variables
private PerformanceCounter cpuCounter = null;
private PerformanceCounter ramCounter = null;
private PerformanceCounter pageCounter = null;
private PerformanceCounter[] nicCounters = null;

// called in Form.Load
private void InitCounters()
{
    try
    {
        cpuCounter = new PerformanceCounter
            ("Processor", "% Processor Time", "_Total", machineName);
        ramCounter = new PerformanceCounter
            ("Memory", "Available MBytes", String.Empty, machineName);
        pageCounter = new PerformanceCounter
            ("Paging File", "% Usage", "_Total", machineName);
    }
    catch (Exception ex)
    {
        SysCounters.Program.HandleException
            (String.Format("Unable to access computer 
            '{0}'\r\nPlease check spelling and verify 
            this computer is connected to the network", 
            this.machineName));
        Close();
    }
}

// called by Timer
private void tTimer_Tick(object sender, EventArgs e)
{
    try
    {
        tsCPU.Text = String.Format("{0:##0} %", cpuCounter.NextValue());
        tsRAM.Text = String.Format("{0} MB", ramCounter.NextValue());
        tsPage.Text = String.Format("{0:##0} %", pageCounter.NextValue());

        for (int i = 0; i  < nicCounters.Length; i++)
        {
            ssStatusBar.Items[String.Format("tsNIC{0}", i)].Text = 
                String.Format("{0:####0 KB}", 
                nicCounters[i].NextValue() / 1024);
        }
    }
    catch (Exception ex)
    {
        // remote computer might have become unavailable; 
        // show exception and close this application
        tTimer.Enabled = false;
        SysCounters.Program.HandleException(ref ex);
        Close();
    }
}
        
// called in Form.Closing
private void DisposeCounters()
{
    try
    {
        // dispose of the counters
        if (cpuCounter != null)
        { cpuCounter.Dispose(); }
        if (ramCounter != null)
        { ramCounter.Dispose(); }
        if (pageCounter != null)
        { pageCounter.Dispose(); }
        if (nicCounters != null)
        {
            foreach (PerformanceCounter counter in nicCounters)
            { counter.Dispose(); }
        }
    }
    finally
    { PerformanceCounter.CloseSharedResources(); }
}

由于一台计算机可以有多个网络适配器,我决定保持简单,只监控每个存在的网络接口的吞吐量(“Bytes Total/sec”),当然,排除“TCP 回环接口”。PerformanceCounterCategory.GetInstanceNames() 返回网络接口实例的字符串数组,我用它来创建相应的 PerformanceCountersToolStripStatusLabels 数组来显示当前值。

由于我无论如何都需要以编程方式为网络接口创建可变数量的计数器,所以我决定在代码中创建所有计数器,而不是使用 PerformanceCounter 组件,这样更一致。

命令行

默认情况下,程序将监控本地计算机上的性能计数器,除非在命令行中指定了远程计算机的名称或 IP 地址。

// specify remote computer to monitor via command line param
private void GetMachineName()
{
    string[] cmdArgs = System.Environment.GetCommandLineArgs();
    if ((cmdArgs != null) && (cmdArgs.Length > 1))
    { this.machineName = cmdArgs[1]; }
}

// ping the remote computer 
private bool VerifyRemoteMachineStatus(string machineName)
{
    try
    {
        using (Ping ping = new Ping())
        {
            PingReply reply = ping.Send(machineName);
            if (reply.Status == IPStatus.Success)
            { return true; }
        }
    }
    catch (Exception ex)
    { 
        // return false for any exception encountered
        // we'll probably want to just shut down anyway
    }
    return false;
}

异常处理保持得相当简单;捕获异常,向用户显示我们的错误消息,然后调用 Form.Close()

由于我打算运行 SysCounters 应用程序的多个实例(每个我正在监控的计算机一个实例),我想保存每个实例的屏幕位置。在浏览了 System.Configuration 命名空间并花费了太多时间尝试各种方法后,我又选择了最简单的选项,即使用应用程序配置文件中的 AppSettings 部分,并为每个实例创建一个键值对。由于这使用了单个应用程序配置文件,因此会带来一些问题;一个用户保存的位置可能会被另一个用户覆盖,并且非管理员用户可能根本无法保存设置。

// Load and save last window position
// simple way to do it so that we can run multiple instances 
// (one per machine) and still have them all save their position
private void LoadSettings()
{
    int iLeft = 0;
    int iTop = 0;

    System.Configuration.Configuration config = 
        ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    try
    {
        // TryParse will default to zero on an invalid value
        int.TryParse(ConfigurationManager.AppSettings[String.Format
            ("{0}-Left", this.machineName)], out iLeft);
        int.TryParse(ConfigurationManager.AppSettings[String.Format
            ("{0}-Top", this.machineName)], out iTop);
    }
    finally
    {
        this.Left = iLeft;
        this.Top = iTop;
        config = null;
    }
}

private void SaveSettings()
{
    System.Configuration.Configuration config = 
        ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    try
    {
        // remove the previously saved values
        config.AppSettings.Settings.Remove
            ring.Format("{0}-Left", this.machineName));
        config.AppSettings.Settings.Remove
            ring.Format("{0}-Top", this.machineName));
        // save our current values
        // this saves to app.config so it may be a permissions issue 
        //or non-admin users
        config.AppSettings.Settings.Add(String.Format("{0}-Left", 
            this.machineName), this.Left.ToString());
        config.AppSettings.Settings.Add(String.Format("{0}-Top", 
            this.machineName), this.Top.ToString());
        config.Save(ConfigurationSaveMode.Modified);
    }
    finally
    { config = null; }
}

需要注意的事项

请注意,我只在 Workgroup 家庭网络上的 Windows XP Pro SP 2 计算机上测试过此程序,因此我没有处理其他网络环境中可能出现的更复杂的权限问题。

一般来说,要在远程计算机上监控性能计数器,您首先需要确保该特定计算机上有几样东西。首先,您有一个非受限的用户帐户。第二,在网络连接属性中启用了“Microsoft 网络的文件和打印共享”。第三,禁用了“使用简单文件共享”(在 Explorer 的“工具 | 文件夹选项 | 查看”中,确保在列表框中取消选中“使用简单文件共享”)。

结论

此应用程序旨在成为一个简单、直接、无需额外配置的“即用即忘”实用程序。可以进行其他调整和更改(按用户配置文件保存设置,最小化到任务托盘,将其转换为 MDI 应用程序,并将每个“监视器”实例作为子窗口),但它对我来说效果很好。

感谢阅读,希望对您有所帮助!

© . All rights reserved.