Quantcast
Channel: asp.net - Steve Hobbs
Viewing all articles
Browse latest Browse all 18

Real-time system resource monitor with SignalR, WCF and KnockoutJS

$
0
0

Application building time! This article is designed to give you, the reader, some grounding in a few different technologies and help you build a working application which may or may not be actually useful. Nonetheless, it should be fun to build and at the end you will hopefully see some nice web wizardry to keep you entertained. Beware – this is a lengthy one.

Here’s what we will build:

  • An MVC web application, exposing a WCF service across SSL
  • A console application to securely send processor and memory information to the service
  • A web page to show these system stats in real time using SignalR and KnockoutJS
These are the technologies we are going to use:

And this is what will happen. The console application will run on the host PC and post system resource usage across a secure HTTPS channel to the WCF service which is running on the website. The service will then send this information straight to the SignalR hub, which will then broadcast the information to all clients, who will display this information on the page.

In this article, I am using Visual Studio 2012 RC with Asp.Net MVC 4 installed.

Before we get started, you should familiarise yourself just a little with SignalR and KnockoutJS as I’m not going to go into the technical concepts behind them, but merely show you how to use them. Both sites have excellent tutorials here and here to get you started. Also, I assume you know your way around Asp.Net MVC.

You can also download the tutorial project and follow along with the finished demo if you like. Note that I have enabled Nuget Package Restore to keep the file size down, so if you do download it, open up your package manager and restore the missing packages first!

The Web Application

We’ll start by creating the web application, as it hosts the WCF service which we require in order to complete the console application in the next section. Start by creating a new MVC application – I used the “Empty” template for a MVC 4 Web Application, but really the version doesn’t matter too much; we just need a web host.

Creating the web project

First, we shall install SignalR. This is very easily done using Nuget, and you can either install this from the Package Manager Console, or through the GUI. The package name is simply called SignalR:

Install-Package SignalR

This will install a number of scripts into the /Scripts folder and also add references to a couple of SignalR managed libraries.

Next, we will create a Hub to manage our connections and pass messages around. Create a new folder within the web project called “Hubs” and add a new class called “CpuInfo”. This class will inherit from SignalR.Hubs.Hub, and will contain a single method for sending CPU data:

using SignalR.Hubs;

namespace WcfCpuApp.Hubs
{
    public class CpuInfo : Hub
    {
        public void SendCpuInfo(string machineName, double processor, int memUsage, int totalMemory)
        {
            this.Clients.cpuInfoMessage(machineName, processor, memUsage, totalMemory);
        }
    }
}

Take note of the call to this.Clients.cpuInfoMessage – this is the call that we need to have a handler for in Javascript (as we will see shortly) and we need to give it the same name.

The next thing to do is set up a page where we can monitor this data. I am going to start this page, then move to the console client application, and then come back to this page to finish it off.

Right-click on the “Controllers” folder and add a new controller called “HomeController”:

Once it has been created, open HomeController.cs, right-click on the Index() action and add a new view. The default settings here are fine:

On this page, we need to do a few things:

  • Include script references for jQuery, SignalR and the SignalR Hub Proxy
  • Write a little bit of script to open the connection to the hub and listen for messages

This is the entirety of my view to accomplish the above:

@{
    ViewBag.Title = "Index";
}

<script type="text/javascript" src="/Scripts/jquery-1.6.4.js"></script>
<script type="text/javascript" src="/Scripts/jquery.signalR-0.5.1.js"></script>
<script type="text/javascript" src="/signalr/hubs"></script>

<div id="computerInfo">

    <h2>My Computer Info</h2>

</div>

<script type="text/javascript">

    $(function () {

        // Get a reference to our hub
        var hub = $.connection.cpuInfo;

        // Add a handler to receive updates from the server
        hub.cpuInfoMessage = function (machineName, cpu, memUsage, memTotal) {
            console.log(machineName, cpu, memUsage, memTotal);
        };

        // Start the connectio
        $.connection.hub.start();
    });

</script>

The key part is the section of Javascript at the bottom, which in itself is reasonably simple (but note again the call to cpuInfoMessage). We just get a reference to the hub context, register our handler for when we receive CPU info, then start the hub. For the moment I just write the data out to the console window, but later I will revisit this page and spice it up a little with the use of some KnockoutJS!

Before you leave this page, make sure you have referenced the correct script files.

Creating the WCF Service

Now we will create the web service that the client console application will connect to. Create a folder called “Services” and insert a new WCF Service called “CpuService”:

Next, open ICpuService.cs and change the contract so that it contains a method which will allow us to send CPU data. Thanks to the nature of our very simple application, the signature looks very similar to the one we used in our CpuInfo hub we created earlier:

using System.ServiceModel;

namespace WcfCpuApp.Services
{
    [ServiceContract]
    public interface ICpuService
    {
        [OperationContract]
        void SendCpuReport(string machineName, double processor, ulong memUsage, ulong totalMemory);
    }
}

The implementation of the service in CpuService.svc.cs is equally as simple:

using WcfCpuApp.Hubs;

namespace WcfCpuApp.Services
{
    public class CpuService : ICpuService
    {
        public void SendCpuReport(string machineName, double processor, ulong memUsage, ulong totalMemory)
        {
            var context = SignalR.GlobalHost.ConnectionManager.GetHubContext<CpuInfo>();
            context.Clients.cpuInfoMessage(machineName, processor, memUsage, totalMemory);
        }
    }
}

Here we just get an instance of the HubContext for our hub type (CpiInfo) which allows us to access various bits of data for our Hub, including any clients and groups which are currently active. Then, we simply call our cpuInfoMessage method, passing in the data. Any clients that are connected will be notified and passed the data, allowing them to process it and – in our case – write it out to the screen.

Finally, we need to configure the service in the web.config file. We will set the service up to use basicHttpBinding with Transport security,  meaning that our data will be transferred securely over HTTPS to our web service. When you added the WCF Service, it will have added a <system.serviceModel> node to your web.confg Xml. You should mofidy it to look like the following:

<system.serviceModel>
	<services>
		<service name="WcfCpuApp.Services.CpuService">
			<endpoint address="" binding="basicHttpBinding" contract="WcfCpuApp.Services.ICpuService" />
		</service>
	</services>

	<bindings>
		<basicHttpBinding>
			<binding>
				<security mode="Transport" />
			</binding>
		</basicHttpBinding>
	</bindings>

	<behaviors>
		<serviceBehaviors>
			<behavior name="">
				<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
				<serviceDebug includeExceptionDetailInFaults="false" />
			</behavior>
		</serviceBehaviors>
	</behaviors>
	<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
		multipleSiteBindingsEnabled="true" />
</system.serviceModel>

Depending on which version of WCF you are using, you will have different amounts of this Xml already there. I’m using 4.0, and I only had the <behaviours> element by default. Make sure that your version contains the <services> and <bindings> element as described in the code snippet above, and that if you are using a different project name and/or namespaces from the ones used in this article, you will need to modify the Wcf bindings in the config to reflect this.

Since we’re using basicHttpBinding with Transport security, we need to make sure our host web application is set up on a secure address and has a certificate installed. IIS Express makes this easy for us, and that is what I am using for this article. We just have to enable SSL on it and it will install a self-signed certificate for us and register a HTTPS base address for us to use. To do this:

  • Right-click on the Project node in Solution Explorer and select Properties
  • In the Web tab, make sure Use Local IIS Web Server is selected and that Use IIS Express is ticked
  • Next, with the project node still selected in Solution Explorer, hit the F4 key to bring up the property panel
  • Set SSL Enabled to ‘true’ under Development Server

Web project settings


At this point, you should be able to see the SSL Url. After compiling the project, you should be able to browse to https://localhost:44300 and view the simple home page that we created earlier. Fire up your favourite browser development tools and just verify that you have no Javascript errors and that all the scripts are being included properly.

If you want, you can also run all this stuff on full-fat IIS 7.0.

The Console Client Project

We shall leave the web side of things for now and have a look at the local client which will send up system resource information to the web service.

The application we will construct will work like this:

  • It opens the service client, ready for sending data
  • It creates a worker thread to do the posting without interfering with the main thread, to keep it responsive
  • It queries a performance counter to get the CPU usage and current memory usage, and WMI to get the total system memory.
  • It sends this data to the WCF service we created earlier, then waits a second before doing it again, until the user hits a key to stop the application.

To start, add a new Console Application project to the solution. I called mine “CpuInfoClient”:

Next, add a Service Reference to this project, using the url of your web application. https://localhost:44300 should work fine:

If the service has been set up correctly and IIS has been configured to use SSL, you should be prompted to accept the self-signed certificate that it will use to secure the data; you should accept it if you are to continue with the sample and complete the project.

Next, lets start filling out the project. Overall, it’s fairly simple but I’ll go through it step-by-step.

Set up some variables to store our “Is running” flag and some performance counters (you will need to add a using statement for these for System.Diagnostics):

static bool _running = true;
static PerformanceCounter _cpuCounter, _memUsageCounter;

Diving into the main() method, we will first add a piece of code which allows us to work with self-signed certificates properly. We’ll also set up some variables for our service client and for the worker thread:

// Required for a self-signed certificate
System.Net.ServicePointManager.ServerCertificateValidationCallback =
    ((sender, certificate, chain, sslPolicyErrors) => true);

Thread pollingThread = null;
CpuInfoService.CpuServiceClient serviceClient = null;

// Hello!
Console.WriteLine("CPU Info Client: Reporting your CPU usage today!");

Note: If you were to use a properly signed certificate from a vendor such as Verisign, you will not need this code. Otherwise, you will probably see an exception being thrown with the following message when you try and invoke a method on the WCF service:

Could not establish trust relationship for the SSL/TLS secure channel with authority

Next, we will dive into a try/catch block and set up performance counters, create the service client and then start the worker thread. To create the performance counters:

try
{
    _cpuCounter = new PerformanceCounter();
    _cpuCounter.CategoryName = "Processor";
    _cpuCounter.CounterName = "% Processor Time";
    _cpuCounter.InstanceName = "_Total";

    _memUsageCounter = new PerformanceCounter("Memory", "Available KBytes");

Then create the service client and start the worker thread:

// Create the service client
serviceClient = new CpuInfoService.CpuServiceClient();

// Create a new thread to start polling and sending the data
pollingThread = new Thread(new ParameterizedThreadStart(RunPollingThread));
pollingThread.Start(serviceClient);

As we will see, I have created a method called RunPollingThread which will send our data up to the service. Also, when I start the thread, I pass in the instance of the service client so that the thread can use it to send up the data.

Finally, still within the main() method, I finish off by writing out some friendly messages to the console, joining the worker thread and then closing the service client. If an exception is thrown, I make sure the service client and the thread are aborted:

    Console.WriteLine("Press a key to stop and exit");
    Console.ReadKey();

    Console.WriteLine("Stopping thread..");

    _running = false;

    pollingThread.Join(5000);
    serviceClient.Close();

}
catch (Exception)
{
    pollingThread.Abort();
    serviceClient.Abort();

    throw;
}

Notice how the _isRunning flag is used. When the user hits a key, the flag is set to “false” and this is the signal to the worker thread to stop doing its thing and exit the thread.

The Worker Thread

The worker thread really just runs in a loop, waiting a second before iterating and sending up local resource data. Here’s the whole method:

static void RunPollingThread(object serviceClient)
{
	// Convert the object that was passed in
    var svc = serviceClient as CpuInfoService.CpuServiceClient;
    DateTime lastPollTime = DateTime.MinValue;

    Console.WriteLine("Started polling...");

	// Start the polling loop
    while (_running)
    {
        // Poll every second
        if ((DateTime.Now - lastPollTime).TotalMilliseconds >= 1000)
        {
            double cpuTime;
            ulong memUsage, totalMemory;

			// Get the stuff we need to send
            GetMetrics(out cpuTime, out memUsage, out totalMemory);

			// Send the data
            svc.SendCpuReport(System.Environment.MachineName, cpuTime, memUsage, totalMemory);

			// Reset the poll time
            lastPollTime = DateTime.Now;
        }
        else
        {
            Thread.Sleep(10);
        }
    }
}

The main thing to be careful of is that you correctly react to the _isRunning flag and exit the thread when that flag drops to “false”. Sending the data through the service is quite easy – as long as the service reference tool completed successfully and the service proxy was generated, you can just call the method and pass in the data. Perhaps the more juicy part lies in the GetMetrics() method, where we actually retrieve system resources:

static void GetMetrics(out double processorTime, out ulong memUsage, out ulong totalMemory)
{
    processorTime = (double)_cpuCounter.NextValue();
    memUsage = (ulong)_memUsageCounter.NextValue();
    totalMemory = 0;

    // Get total memory from WMI
    ObjectQuery memQuery = new ObjectQuery("SELECT * FROM CIM_OperatingSystem");

    ManagementObjectSearcher searcher = new ManagementObjectSearcher(memQuery);

    foreach (ManagementObject item in searcher.Get())
    {
        totalMemory = (ulong)item["TotalVisibleMemorySize"];
    }
}

The first section of code uses the two performance counters we created earlier to retrieve the values for the current CPU usage, and the current memory usage. Finally, we use WMI to retrieve the available system memory. The units here are in Kilobytes.

Testing it out

So, if the service reference worked and everything compiles, you should now be able to start the website and browse to the home page, run the console client and then see your system resource values being printed out to the browser console window!

Wrapping it up

Now that we’ve verified we can send data to the browser from a desktop client in a real-time way, if you’re quite happy with that then you can stop here. What I’m going to do to finish off is write this data to some Html elements using a bit of KnockoutJS, so that we can see it on the page without having to dive into the browser console.

First of all, head back into the web project, and install the following Nuget packages, either through the GUI or from the Package Manager Console:

Install-Package KnockoutJS
Install-Package Knockout.Mapping

This will install a few scripts into the /Scripts folder of your site, which are the debug and minified versions of KnockoutJS and the mapping plugin.

Open up Index.cshtml with the SignalR code on it that we completed earlier, and register these two scripts on the page, making sure the mapping plugin is included after Knockout:

<script type="text/javascript" src="/Scripts/knockout-2.1.0.js"></script>
<script type="text/javascript" src="/Scripts/knockout.mapping-latest.js"></script>

Next, we want to include some markup to display our data on the page:

<div id="computerInfo">

    <h2>My Computer Info</h2>

    <table border="0">
        <tr>
            <th>Machine</th>
            <th>CPU %</th>
            <th>Memory Available (Mb)</th>
            <th>Total Memory (Mb)</th>
            <th>Mem Available %</th>
        </tr>

        <!-- ko foreach: machines -->
        <tr data-bind="css: { highCpu: cpu() > 90 }">
            <td data-bind="text: machineName"></td>
            <td data-bind="text: cpu"></td>
            <td data-bind="text: memUsage"></td>
            <td data-bind="text: memTotal"></td>
            <td data-bind="text: memPercent"></td>
        </tr>
        <!-- /ko -->

    </table>

</div>

The key thing about this block of Html is the Knockout bindings. Here, I say that “for each item in our ‘machines’ array, write out this table row”. This is using the new containerless control flow syntax from Knockout 2.0, and allows us to perform iteration loops and conditional statements without the need for a containing element cluttering up the page. Also, inside the row, notice how I am just binding each cell to a property on my metric data. Finally, using the CSS binding, I am applying a style named ‘highCpu’ whenever the value of ‘cpu’ rises above 90% – in my case, this style renders the entire row with a red background to make it a bit more visual.

Now let’s dive into the script. Right above where we added our SignalR code earlier, we need to create a view model to bind to our table data:

var ViewModel = function () {

    var self = this;

    self.machines = ko.observableArray();

};

var vm = new ViewModel();
ko.applyBindings(vm, $("#computerInfo")[0]);

As you can see, it’s pretty simple – we just have an array for our machine data, and we bind it straight away to our div which contains our table element. It is important that we hang on to a reference to the model that we bind to the div, as we will need it in this next bit.

Earlier, when we wired up our SignalR code, we simply wrote out the CPU data to the console window in the event handler that gets called whenever data is sent to the hub. We need to replace that code with the following. First up, lets create an object which contains all of our data and formats it nicely:

var machine = {
    machineName: machineName,
    cpu: cpu.toFixed(0),
    memUsage: (memUsage / 1024).toFixed(2),
    memTotal: (memTotal / 1024).toFixed(2),
    memPercent: ((memUsage / memTotal) * 100).toFixed(1) + "%"
};

Next, convert it to a proper view model so that Knockout can listen for changes to the data and update the UI accordingly. This is where the mapping plugin comes in handy – it takes all the properties on our simple object and converts them to observables!

var machineModel = ko.mapping.fromJS(machine);

Now check if we already have an entry using the same machine name. If we do, update the one we already have; otherwise, add it to the ‘machines’ array:

var match = ko.utils.arrayFirst(vm.machines(), function (item) {
    return item.machineName() == machineName;
});

if (!match)
    vm.machines.push(machineModel);
else {
    var index = vm.machines.indexOf(match);
    vm.machines.replace(vm.machines()[index], machineModel);
}

We’re using ko.utils.arrayFirst here to find an existing item by its machine name property. If it’s found, we will get the item returned to us in match. If it’s not set, then we push it onto the machines array, effectively adding another row to the table. If it is set, find the index of that match in the array, and replace the item with the one we have just created, effectively updating the data.

All that’s left now is a little styling – add this just before the containing element with our table in it:

<style type="text/css">
    table {
        width: 700px;
    }

    body {
        font-family: "Segoe UI", Arial, sans-serif;
        font-size: 12px;
    }

    .highCpu {
        background: red;
        color: white;
    }
</style>

And there you have it!

And here’s a crude snapshot taken on my phone to prove it’s running across two machines!

Download the project source


Viewing all articles
Browse latest Browse all 18

Trending Articles