(Print this page)

Monitoring computer performance from a .Net application
Published date: Wednesday, June 26, 2019
On: Moer and Éric Moreau's web site

I have a user complaining about the performance of his computer when using a commercial application (not one of mine!). He is experiencing serious slowdowns for a few minutes while the markets are highly volatile. Of course, he is just blaming the computer. And the user is not ready to do anything to help diagnosing the issue. Not ready to close his Spotify or his Outlook or anything else just to see if that could help identify the issue. We even offer to install a second computer on his desk to run anything other than this critical application but refuses to do use it. In short, he is a jerk! I hope you don’t have to many users like this.

The first thing I wanted to check were the resources on his computer. I found a few tools, but none were straight to the point. What I really wanted was something like the Windows Task Manager for a remote computer that would let me save values, so I don’t have to sit all day long in front of a screen waiting for slowdowns to happen.

I have used my favorite search engine and discovered that a few WMI (Windows Management Instrumentation) calls would let me achieve my requirements. Much like what we see in the Windows Task Manager, I was trying to get the usage of the CPU, memory, disk, and network to see if one of these is the bottleneck.

Available source code

The downloadable demo solution has been created using Visual Studio 2019. Both VB and C# code are available. Even if the solution has been created using VS2019, the code can surely be used in previous versions.

Need to give access to the firewall

Before trying to run this code and be able to connect to remote computers on your network, you will surely need to create new rules in your firewall (on each computer you want to monitor).

From the “Windows Defender Firewall with Advanced Security” dialog, click on “Inbound Rules” and then “New Rule…”.

From the “New Inbound Rule Wizard”, click on Predefined and select “Windows Management Instrumentation (WMI)” from the drop-down list. This is shown on figure 1. Click the Next button.

Figure 1: Enabling firewall

Then, as shown on figure 2, you need to check “Windows Management Instrumentation (WMI-In)” in the Domain profile. Click the Next button.

Figure 2: Selecting the correct rule.

In the last step of the wizard, just select “Allow the connection” and click the Finish button.

if you need help to open this dialog, visit this page. My preferred way of opening the dialog is to type “wf.msc” (without the quotes) in the Run command dialog.

If you have other errors when trying to connect to other computers, have a look at this page.

Reference

Because we will use WMI in this project, we need to add a reference to the System.Management file to your project as shown in figure 3.

Figure 3: Reference needed for System.Management

Building the UI

A simple UI to let you enter some parameters and start/stop the monitoring.

Figure 4: The demo application in action

In this UI, you can specify the computers list (comma separated values), the admin user and its password. This user needs elevated privileges on the computers. Once the monitoring has started, you will see values filling the DataGrid. It is up to you to either save them, show them in a chart, … The RichTextBox at the bottom shows some logs entries in color.

The PerfRow class

The bulk of the code and calculations are contained in the PerfRow class. Each computer that you are monitoring has an instance of that class. This is mainly because connecting to a remote computer can take quite some time and we don’t want to go through that process every time we need to get new values (every 5 seconds in my case).

The full class reads like this:

Option Strict On

Imports System.Management
Imports System.Net

Public Class PerfRow

    Private _managementObjectCpu, _managementObjectDisk As ManagementObject
    Private _managementScope As ManagementScope
    Private _managementObjectCollection As ManagementObjectCollection
    Private _managementClassMemory, _managementClassNetwork As ManagementClass
    Private _oldCpuV1, _oldCpuV2 As ULong
    Private ReadOnly _hostName, _userName, _password As String
    Private _numberOfCores As Integer = -1

    Public Sub New(ByVal pHostName As String, ByVal pUserName As String, ByVal pPassword As String)
        _hostName = pHostName
        _userName = pUserName
        _password = pPassword
    End Sub

    Public Function Connect() As Boolean
        If Dns.GetHostName() = _hostName Then
            'local computer
            _managementScope = New ManagementScope("\\" & _hostName & "\root\cimv2")
        Else
            'remote computer
            Dim options As New ConnectionOptions With {
                .Username = _userName,
                .Password = _password
            }

            'scope object for remote machine
            _managementScope = New ManagementScope("\\" & _hostName & "\root\cimv2", options)
        End If

        Try
            _managementScope.Connect()
        Catch exception1 As UnauthorizedAccessException
            Return False
        Catch exception As Runtime.InteropServices.COMException
            Return False
        Catch
            Return False
        End Try

        'Number of cores - only read once. 
        Dim objProcessorsAll As ManagementPath = New ManagementPath("Win32_PerfRawData_PerfOS_Processor")
        Dim managementObjectCpus As ManagementClass = New ManagementClass(_managementScope, objProcessorsAll, Nothing)
        _numberOfCores = managementObjectCpus.GetInstances().Count - 1

        'CPU %
        Dim objCpu As ManagementPath = New ManagementPath("Win32_PerfRawData_PerfOS_Processor.Name='_Total'")
        _managementObjectCpu = New ManagementObject(_managementScope, objCpu, Nothing)

        'Memory Available 
        Dim objMemory As ManagementPath = New ManagementPath("Win32_PerfRawData_PerfOS_Memory")
        _managementClassMemory = New ManagementClass(_managementScope, objMemory, Nothing)

        'Disk % 
        Dim objDisk As ManagementPath = New ManagementPath("Win32_PerfFormattedData_PerfDisk_LogicalDisk.Name='C:'")
        _managementObjectDisk = New ManagementObject(_managementScope, objDisk, Nothing)

        'Network % 
        Dim objNetwork As ManagementPath = New ManagementPath("Win32_PerfFormattedData_Tcpip_NetworkInterface")
        _managementClassNetwork = New ManagementClass(_managementScope, objNetwork, Nothing)
        Return True
    End Function

    Public ReadOnly Property NumberOfCores As Object
        Get
            Return _numberOfCores
        End Get
    End Property

    Public Function GetDisk() As Decimal
        _managementObjectDisk.[Get]()
        Return (CULng(_managementObjectDisk.Properties("PercentIdleTime").Value))
    End Function

    Public Function GetCpu() As Decimal
        Dim percentProcessorTime As Decimal

        Try
            _managementObjectCpu.[Get]()

            Dim lngNewCpu As ULong = CULng(_managementObjectCpu.Properties("PercentProcessorTime").Value)
            Dim lngNewNano As ULong = CULng(_managementObjectCpu.Properties("TimeStamp_Sys100NS").Value)

            Dim decNewCpu As Decimal = Convert.ToDecimal(lngNewCpu)
            Dim decNewNano As Decimal = Convert.ToDecimal(lngNewNano)
            Dim decOldCpu As Decimal = Convert.ToDecimal(_oldCpuV1)
            Dim decOldNano As Decimal = Convert.ToDecimal(_oldCpuV2)

            'Thanks to MSDN for giving me this formula !
            percentProcessorTime = (1 - ((decNewCpu - decOldCpu) / (decNewNano - decOldNano))) * 100D

            _oldCpuV1 = lngNewCpu
            _oldCpuV2 = lngNewNano
        Catch
            percentProcessorTime = 0
        End Try

        If percentProcessorTime < 0 Then percentProcessorTime = 0

        Return percentProcessorTime
    End Function

    Public Function GetMemory() As Decimal
        Dim uMem As ULong = 0

        Try
            _managementObjectCollection = _managementClassMemory.GetInstances()

            For Each objMem As ManagementBaseObject In _managementObjectCollection
                uMem += CULng(objMem.Properties("AvailableMBytes").Value)
            Next

        Catch
            uMem = 0
        End Try

        Return (uMem / 1000D)
    End Function

    Public Function GetNetwork() As Decimal
        Try
            _managementObjectCollection = _managementClassNetwork.GetInstances()

            For Each baseObject As ManagementBaseObject In _managementObjectCollection
                Dim lngBytesTotal As ULong = CULng(baseObject.Properties("BytesTotalPersec").Value)
                Dim lngBandwidth As ULong = CULng(baseObject.Properties("CurrentBandwidth").Value)

                If lngBandwidth = 0 Then Return 0

                Dim decUtilization As Decimal = ((8 * lngBytesTotal) / CDec(lngBandwidth)) * 100
                Return decUtilization
            Next

            Return 0
        Catch e As Exception
            Console.WriteLine(e)
            Return 0
        End Try
    End Function

    Public ReadOnly Property IsConnected As Boolean
        Get
            Return _managementScope.IsConnected
        End Get
    End Property

End Class
using System;
using System.Management;
using System.Net;

namespace DemoPerfMonitorCS
{
    public class PerfRow
    {

        private ManagementObject _managementObjectCpu, _managementObjectDisk;
        private ManagementScope _managementScope;
        private ManagementObjectCollection _managementObjectCollection;
        private ManagementClass  _managementClassMemory, _managementClassNetwork;
        private ulong _oldCpuV1, _oldCpuV2;
        private readonly string _hostName, _userName, _password;
        private int _numberOfCores = -1;

        public PerfRow(string pHostName, string pUserName, string pPassword)
        {
            _hostName = pHostName;
            _userName = pUserName;
            _password = pPassword;
        }

        public bool Connect()
        {
            if (Dns.GetHostName() == _hostName)
            {
                //local computer
                _managementScope = new ManagementScope("\\\\" + _hostName + "\\root\\cimv2");
            }
            else
            {
                //remote computer
                ConnectionOptions options = new ConnectionOptions
                {
                    Username = _userName,
                    Password = _password
                };

                // scope object for remote machine
                _managementScope = new ManagementScope("\\\\" + _hostName + "\\root\\cimv2", options);
            }

            try
            {
                _managementScope.Connect();
            }
            catch (UnauthorizedAccessException)
            {
                return false;
            }
            catch (System.Runtime.InteropServices.COMException)
            {
                return false;
            }
            catch
            {
                return false;
            }

            // Number of cores - only read once. 
            ManagementPath objProcessorsAll = new ManagementPath("Win32_PerfRawData_PerfOS_Processor");
            ManagementClass managementObjectCpus = new ManagementClass(_managementScope, objProcessorsAll, null);
            _numberOfCores = managementObjectCpus.GetInstances().Count - 1;

            // CPU %
            ManagementPath objCpu = new ManagementPath("Win32_PerfRawData_PerfOS_Processor.Name='_Total'");
            _managementObjectCpu = new ManagementObject(_managementScope, objCpu, null);

            // Memory Available 
            ManagementPath objMemory = new ManagementPath("Win32_PerfRawData_PerfOS_Memory");
            _managementClassMemory = new ManagementClass(_managementScope, objMemory, null);

            // Disk %            
            ManagementPath objDisk = new ManagementPath("Win32_PerfFormattedData_PerfDisk_LogicalDisk.Name='C:'");
            _managementObjectDisk = new ManagementObject(_managementScope, objDisk, null);

            // Network %            
            ManagementPath objNetwork = new ManagementPath("Win32_PerfFormattedData_Tcpip_NetworkInterface");
            _managementClassNetwork = new ManagementClass(_managementScope, objNetwork, null);

            return true;
        }

        public object NumberOfCores
        {
            get { return _numberOfCores; }
        }


        public decimal GetDisk()
        {
            _managementObjectDisk.Get();
            return ((ulong)_managementObjectDisk.Properties["PercentIdleTime"].Value);
        }

        public decimal GetCpu()
        {
            decimal percentProcessorTime;
            try
            {
                _managementObjectCpu.Get();

                ulong lngNewCpu = (ulong)_managementObjectCpu.Properties["PercentProcessorTime"].Value;
                ulong lngNewNano = (ulong)_managementObjectCpu.Properties["TimeStamp_Sys100NS"].Value;

                decimal decNewCpu = Convert.ToDecimal(lngNewCpu);
                decimal decNewNano = Convert.ToDecimal(lngNewNano);
                decimal decOldCpu = Convert.ToDecimal(_oldCpuV1);
                decimal decOldNano = Convert.ToDecimal(_oldCpuV2);

                // Thanks to MSDN for giving me this formula !
                percentProcessorTime = (1 - ((decNewCpu - decOldCpu) / (decNewNano - decOldNano))) * 100m;

                _oldCpuV1 = lngNewCpu;
                _oldCpuV2 = lngNewNano;
            }
            catch 
            {
                percentProcessorTime = 0;
            }
            if (percentProcessorTime < 0)
                percentProcessorTime = 0;

            return percentProcessorTime;
        }

        public decimal GetMemory()
        {
            ulong uMem = 0;

            try
            {
                _managementObjectCollection = _managementClassMemory.GetInstances();

                foreach (ManagementBaseObject objMem in _managementObjectCollection)
                {
                    uMem += (ulong)objMem.Properties["AvailableMBytes"].Value;
                }
            }
            catch 
            {
                uMem = 0;
            }

            return (uMem / 1000m);
        }

        public decimal GetNetwork()
        {
            try
            {
                _managementObjectCollection = _managementClassNetwork.GetInstances();

                foreach (ManagementBaseObject baseObject in _managementObjectCollection)
                {
                    ulong lngBytesTotal = (ulong)baseObject.Properties["BytesTotalPersec"].Value;
                    ulong lngBandwidth = (ulong)baseObject.Properties["CurrentBandwidth"].Value;

                    if (lngBandwidth == 0)
                        return 0;

                    decimal decUtilization = ((8 * lngBytesTotal) / (decimal)lngBandwidth) * 100;
                    return decUtilization;
                }
                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return 0;
            }
        }

        public bool IsConnected
        {
            get
            {
                return _managementScope.IsConnected;
            }
        }

    }
}

As you can see in this code, WMI is used to connect to the computer and get some values out of it. Some values are quite simple to get like the one to know the idle time of the C: drive (see the GetDisk method). Other values are more complex to get. Network and CPU usage are good examples.

I found some interesting links while looking on how to extract these values:

The main process

When you click the “start” button, the values of the 3 textboxes at the top of the UI will be used and instances of the PerfRow class will be created trying to connect to these computers. Hopefully it will be able to connect.

Once the computers are connected, the timer is started. In my case, the interval is set to 5 seconds. Every time the Tick event is triggered, the various values of CPU, memory, disk and network are retrieved from the PerfRow class and stored in the DataTable to which the DataGrid is bound to.

The full code of the test form reads like this:

Option Strict On

Imports System.Net.NetworkInformation
Imports System.Text

Public Class Form1

    Inherits Form

    Private _objPerfRow As PerfRow()
    Private _dtResults As DataTable
    Private _arrayOfMachineName As String()

    Public Sub New()
        InitializeComponent()
        btnStart.Text = "Start Monitor"
        txtComputers.Text = "station23,station27,MOER-7060"
        txtUser.Text = "PutYourAdminAccountHere" 
        txtPassword.Text = "PutYourAdminPasswordHere"
    End Sub

    Private Sub BtnStart_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnStart.Click
        If btnStart.Text.ToLower() = "start monitor" Then
            WriteLog("Monitoring is starting", Color.Blue)
            LoadComputers()
            btnStart.Text = "Stop Monitor"
            timer1_Tick(sender, e)
        Else
            WriteLog("Monitoring is stopping", Color.Red)
            timer1.Enabled = False
            btnStart.Text = "Start Monitor"
        End If
    End Sub

    Private Sub LoadComputers()
        InitializeGrid()

        If String.IsNullOrWhiteSpace(txtComputers.Text) OrElse String.IsNullOrWhiteSpace(txtUser.Text) OrElse String.IsNullOrWhiteSpace(txtPassword.Text) Then
            WriteLog("You need to provide a computer list, an admin user and password", Color.Red)
            Return
        End If

        _arrayOfMachineName = txtComputers.Text.Split(","c)
        _objPerfRow = New PerfRow(_arrayOfMachineName.Length - 1) {}

        _dtResults.Rows.Clear()

        WriteLog("Loading Machines Info...", Color.DarkGreen)

        Try

            For i As Integer = 0 To _arrayOfMachineName.Length - 1
                WriteLog($"Trying to access computer {_arrayOfMachineName(i)}...", Color.Orange)
                _objPerfRow(i) = New PerfRow(_arrayOfMachineName(i), txtUser.Text, txtPassword.Text)

                If _objPerfRow(i).Connect() Then
                    Dim newRow As DataRow = _dtResults.NewRow()
                    newRow(0) = _arrayOfMachineName(i)
                    newRow(5) = _objPerfRow(i).NumberOfCores
                    _dtResults.Rows.Add(newRow)
                    WriteLog($"Successfully connected to computer {_arrayOfMachineName(i)}", Color.Chocolate)
                Else
                    WriteLog($"Unable to access computer {_arrayOfMachineName(i)}. Please check user id and password", Color.Red)
                End If
            Next

        Catch e As Exception
            WriteLog(e.Message, Color.Red)
        End Try
    End Sub

    'Initializes Datagrid	
    Private Sub InitializeGrid()
        _dtResults = New DataTable("Counters")
        _dtResults.Columns.Add("HostName")
        _dtResults.Columns.Add("CPU %")
        _dtResults.Columns.Add("Free Memory (in Gb)")
        _dtResults.Columns.Add("Disk Idle time %")
        _dtResults.Columns.Add("Network %")
        _dtResults.Columns.Add("Cores")
        dataGrid1.DataSource = _dtResults
    End Sub

    'Call every 5 seconds. Gets new values for CPU, Memory and Disk for each row in the grid
    Private Sub timer1_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles  timer1.Tick
        Dim i As Integer = 0
        timer1.Stop()

        Do While i < _arrayOfMachineName.Length
            Debug.WriteLine($"{i} - {_arrayOfMachineName(i)}")

            Try
                _dtResults.Rows(i)(0) = _arrayOfMachineName(i)

                If IsReachable(_arrayOfMachineName(i)) Then 'ping if host is reachable

                    If _objPerfRow(i).IsConnected Then 'check if still connected
                        Try
                            Dim decCpu As Decimal = _objPerfRow(i).GetCpu()
                            _dtResults.Rows(i)(1) = decCpu.ToString("##0.00")

                            Dim decMemory As Decimal = _objPerfRow(i).GetMemory()
                            _dtResults.Rows(i)(2) = decMemory.ToString("##0.0000")

                            Dim decDisk As Decimal = _objPerfRow(i).GetDisk()
                            _dtResults.Rows(i)(3) = decDisk

                            Dim decNetwork As Decimal = _objPerfRow(i).GetNetwork()
                            _dtResults.Rows(i)(4) = decNetwork.ToString("##0.0000")
                        Catch
                            Try
                                'if there is error while retrieving data then try reconnect
                                _objPerfRow(i).Connect()
                            Catch ex As Exception
                                WriteLog($"Error reconnecting to {_arrayOfMachineName(i)} - {ex.Message}", Color.Red)
                            End Try
                        End Try
                    Else
                        Try
                            'if there is error while retrieving data then try reconnect
                            _objPerfRow(i).Connect()
                        Catch ex As Exception
                            WriteLog($"Error reconnecting to {_arrayOfMachineName(i)} - {ex.Message}", Color.Red)
                        End Try
                    End If
                Else
                    _dtResults.Rows(i)(1) = "offline"
                    _dtResults.Rows(i)(2) = "offline"
                    _dtResults.Rows(i)(3) = "offline"
                    _dtResults.Rows(i)(4) = "offline"
                End If

            Catch ex As Exception
                WriteLog($"Error retrieving info of {_arrayOfMachineName(i)} - {ex.Message}", Color.Red)
            End Try

            i += 1
        loop

        timer1.Start()
    End Sub

    Public Function IsReachable(ByVal pHost As String) As Boolean
        Dim pingSender As Ping = New Ping()
        Dim options As PingOptions = New PingOptions With {.DontFragment = True}
        Dim data As String = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
        Dim buffer As Byte() = Encoding.ASCII.GetBytes(data)
        Dim reply As PingReply = pingSender.Send(pHost, 120, buffer, options)
        If reply Is Nothing Then Return False
        If reply.Status = IPStatus.Success Then Return True
        Return False
    End Function

    Public Sub WriteLog(ByVal pMessage As String, ByVal pColor As Color)
        WriteLog(pMessage, pColor, False)
    End Sub

    Private Sub WriteLog(ByVal pMessage As String, ByVal pColor As Color, ByVal pIsHighlight As Boolean)
        txtLogs.SelectionColor = pColor

        If pIsHighlight Then
            Dim ifont As Font = New Font("Arial", 9, FontStyle.Bold)
            txtLogs.SelectionFont = ifont
        Else
            Dim ifont As Font = New Font("Arial", 8)
            txtLogs.SelectionFont = ifont
        End If

        If txtLogs.Lines.Length >= 1000 Then 'limit to 1000 lines only
            txtLogs.Text = ""
        End If

        txtLogs.AppendText($"[{DateTime.Now}] {pMessage}" + Environment.NewLine)
        Refresh()
        If Not txtLogs.Focused Then txtLogs.Focus()
    End Sub
End Class
using System;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Net.NetworkInformation;
using System.Text;
using System.Windows.Forms;

namespace DemoPerfMonitorCS
{
    public partial class Form1 : Form
    {
        private PerfRow[] _objPerfRow;
        private DataTable _dtResults;
        private string[] _arrayOfMachineName; 

        public Form1()
        {
            InitializeComponent();

            btnStart.Text = "Start Monitor";
            txtComputers.Text = "station23,station27,MOER-7060";
            txtUser.Text = "PutYourAdminAccountHere"; 
            txtPassword.Text = "PutYourAdminPasswordHere"; 
        }

        private void BtnStart_Click(object sender, EventArgs e)
        {
            if (btnStart.Text.ToLower() == "start monitor")
            {
                WriteLog("Monitoring is starting", Color.Blue);
                LoadComputers();
                btnStart.Text = "Stop Monitor";
                timer1_Tick(sender, e);
            }
            else
            {
                WriteLog("Monitoring is stopping", Color.Red);
                timer1.Enabled = false;
                btnStart.Text = "Start Monitor";
            }
        }

        private void LoadComputers()
        {
            InitializeGrid();

            if (string.IsNullOrWhiteSpace(txtComputers.Text) ||
                string.IsNullOrWhiteSpace(txtUser.Text) ||
                string.IsNullOrWhiteSpace(txtPassword.Text))
            {
                WriteLog("You need to provide a computer list, an admin user and password", Color.Red);
                return;
            }

            _arrayOfMachineName = txtComputers.Text.Split(',');
            _objPerfRow = new PerfRow[_arrayOfMachineName.Length];

            _dtResults.Rows.Clear();

            WriteLog("Loading Machines Info...", Color.DarkGreen);
            try
            {
                for (int i = 0; i < _arrayOfMachineName.Length; i++)
                {
                    WriteLog($"Trying to access computer {_arrayOfMachineName[i]}...", Color.Orange);

                    _objPerfRow[i] = new PerfRow(_arrayOfMachineName[i], txtUser.Text, txtPassword.Text);
                    if (_objPerfRow[i].Connect())
                    {
                        DataRow newRow = _dtResults.NewRow();
                        newRow[0] = _arrayOfMachineName[i];
                        newRow[5] = _objPerfRow[i].NumberOfCores;
                        _dtResults.Rows.Add(newRow);
                        WriteLog($"Successfully connected to computer {_arrayOfMachineName[i]}", Color.Chocolate);
                    }
                    else
                    {
                        WriteLog($"Unable to access computer {_arrayOfMachineName[i]}. Please check user id and password", Color.Red);
                    }
                }
            }
            catch (Exception e)
            {
                WriteLog(e.Message, Color.Red);
            }
        }

        /* Initializes Datagrid	 */
        private void InitializeGrid()
        {
            _dtResults = new DataTable("Counters");

            _dtResults.Columns.Add("HostName");
            _dtResults.Columns.Add("CPU %");
            _dtResults.Columns.Add("Free Memory (in Gb)");
            _dtResults.Columns.Add("Disk Idle time %");
            _dtResults.Columns.Add("Network %");
            _dtResults.Columns.Add("Cores");

            dataGrid1.DataSource = _dtResults;
        }

        /* Call every 5 seconds. Gets new values for CPU, Memory and Disk for each row in the grid  */
        private void timer1_Tick(object sender, EventArgs e)
        {
            int i = 0;
            timer1.Stop();
            timer1.Enabled = false;

            while (i < _arrayOfMachineName.Length)
            {
                Debug.WriteLine($"{i} - {_arrayOfMachineName[i]}");  
                try
                {
                    _dtResults.Rows[i][0] = _arrayOfMachineName[i];
                    if (IsReachable(_arrayOfMachineName[i])) //ping if host is reachable
                    {
                        if (_objPerfRow[i].IsConnected) //check if still connected
                        {
                            try
                            {
                                decimal decCpu = _objPerfRow[i].GetCpu();
                                _dtResults.Rows[i][1] = decCpu.ToString("##0.00");

                                decimal decMemory = _objPerfRow[i].GetMemory();
                                _dtResults.Rows[i][2] = decMemory.ToString("##0.0000");

                                decimal decDisk = _objPerfRow[i].GetDisk();
                                _dtResults.Rows[i][3] = decDisk;

                                decimal decNetwork = _objPerfRow[i].GetNetwork();
                                _dtResults.Rows[i][4] = decNetwork.ToString("##0.0000");
                            }
                            catch
                            {
                                try
                                {
                                    //if there is error while retrieving data then try reconnect
                                    _objPerfRow[i].Connect();
                                }
                                catch (Exception ex)
                                {
                                    WriteLog($"Error reconnecting to {_arrayOfMachineName[i]} - {ex.Message}", Color.Red);
                                }
                            }
                        }
                        else
                        {
                            try
                            {
                                //if there is error while retrieving data then try reconnect
                                _objPerfRow[i].Connect();
                            }
                            catch (Exception ex)
                            {
                                WriteLog($"Error reconnecting to {_arrayOfMachineName[i]} - {ex.Message}", Color.Red);
                            }
                        }
                    }
                    else
                    {
                        _dtResults.Rows[i][1] = "offline";
                        _dtResults.Rows[i][2] = "offline";
                        _dtResults.Rows[i][3] = "offline";
                        _dtResults.Rows[i][4] = "offline";
                    }
                }
                catch (Exception ex)
                {
                    WriteLog($"Error retrieving info of {_arrayOfMachineName[i]} - {ex.Message}", Color.Red);
                }
                i++;
            }

            timer1.Enabled = true;
            timer1.Start();
        }

        public bool IsReachable(string pHost)
        {
            Ping pingSender = new Ping();
            PingOptions options = new PingOptions { DontFragment = true };
            string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
            byte[] buffer = Encoding.ASCII.GetBytes(data);
            PingReply reply = pingSender.Send(pHost, timeout: 120, buffer, options);
            if (reply == null)
                return false;
            if (reply.Status == IPStatus.Success)
                return true;
            return false;
        }

        public void WriteLog(string pMessage, Color pColor)
        {
            WriteLog(pMessage, pColor, false);
        }

        private void WriteLog(string pMessage, Color pColor, bool pIsHighlight)
        {
            txtLogs.SelectionColor = pColor;
            if (pIsHighlight)
            {
                Font ifont = new Font("Arial", 9, FontStyle.Bold);
                txtLogs.SelectionFont = ifont;
            }
            else
            {
                Font ifont = new Font("Arial", 8);
                txtLogs.SelectionFont = ifont;
            }

            if (txtLogs.Lines.Length >= 1000) //limit to 1000 lines only
            {
                txtLogs.Text = "";
            }

            txtLogs.AppendText($"\r\n[{DateTime.Now}] {pMessage}");
            Refresh();
            if (!txtLogs.Focused)
                txtLogs.Focus();
        }

    }

}

Notice that the Tick event prevents re-entrant code by disabling the timer before doing anything and restarts it after the code has fully executed. That means that you will not get new values every 5 seconds. You will get new values roughly 5 seconds after the end of the last execution.

Conclusion

I have learned new things in this project. The first thing that was confirmed is that I hate users who know-it-all-about-computers and blame the computers without any reasons other their friends working elsewhere have Xeon processor computer instead of a top I7 processor.

With my monitoring, I have been able in a couple of hours to find that the user was running short in memory. We have added an extra 16gb of RAM which helped the overall performance of the computer but did not fix this specific issue. If you are an old timer like me, you know what a paged file on disk is. But do you know that Windows 10 also have in-memory compression?

You want to know what the issue was? An incorrect network configuration causing a lot of packet loss between the switch and the vendor’s platform. Blaming the computer is easy but monitoring the resources proved that the computer was more then fine (once the memory issue solved).


(Print this page)