Thursday, January 27, 2011

NetMonitor. Making UI more efficient

After releasing the first more-or-less useful version of NetMonitor, I’ve been playing with it in different environments and situations, trying to realize whether the current UI is efficient enough for providing useful information about the network health in a form that can be grasped quickly, without spending noticeable amount of time on data parsing and understanding. Also, I’ve been observing the usage of controls that are located at the main window.

Here is how it looked originally:

 NetMonitor UI zones

After a week or two, I’ve made the following observations:

1. Buttons “Start” & “Stop” in this tiny application are useless at all. I’ve noticed that while I had to push “Start” button in order to begin the monitoring process, I never pushed “Stop” button when I wanted to stop the monitoring, because closing the application seemed more natural to me as a regular user. Also, I’ve realized that pushing “Start” button is the excess step, I’d prefer an application, at least this simple one, would start functioning immediately, without additional clicks. So, I’m removing the toolbar with the buttons and launching the monitoring right from the start.

2. Information that is presented in Data panel was also a subject for the investigation. I realized that some columns are more important and other are less. For example, columns “Host” and “Response time” were those I paid much attention to, while “Address” and “Health” were less convenient for me. As for “Host” vs. “Address”: I think that a human-being prefers to operate with more natural textual names for hosts instead of IPs or internet addresses with those “www”, “com”, etc. Moreover, internet address is required for application only, a user wants to know how much is response time for “my router”, “my vpn server”, “my website”, etc., and not for “192.168.0.1”, “10.0.0.66” or other stuff constructed from weird combination of digits and dots.

NetMonitor important columns

So, first of all I’ve removed the toolbar. That was rather easy and didn’t take much time. The most difficult task was how to reorganize the data presentation to provide better visualization. I’ve been experimenting with various patterns and understood that there couldn’t be a good solution unless I refuse using DataGrid as a presentation pattern. So I began probing with direct item templates for `ListView` control. After a while a fresh new look for NetMonitor was born:

Fresh new look for NetMonitor

This information layout focuses on the important data: human readable host names and response time, while other potentially useful information such as IP address, is less expressed. Moreover, the important pieces of data are located closer to each other and it is more convenient for eyes and brain to grasp the connected pieces of information.

The solution was simple. I removed `ListView.View` and nested `GridView` nodes and add a DataTemplate for a ListView. Nothing special:

<ListView.ItemTemplate>
  <DataTemplate>
    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
      </Grid.RowDefinitions>
      <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Name}"
         FontSize="18.667" Margin="5,0,0,0" />
      <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ResponseTime}"
         FontSize="18.667" HorizontalAlignment="Right" Margin="0,0,5,0" />
      <TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Address}"
         FontSize="10.667" Margin="5,0,0,5" />
      <TextBlock Grid.Row="1" Grid.Column="1" Text="ping"
         FontSize="10.667" HorizontalAlignment="Right" Margin="0,0,5,5" />
    </Grid>
  </DataTemplate>
</ListView.ItemTemplate>

After that small achievement I got closer to the point of feeling the dark power of WPF =)

P.S.
The repository is updated, binaries are available here, as usual.

Thursday, January 20, 2011

Gosh! Someone is damn good at support

support.x-tensive.com

Just a funny case, notice the amount of karma =)

Friday, January 14, 2011

NetMonitor. Making application configurable

After we made the application working, the next step is to make it configurable, so a user won’t need to recompile the program when he wants to add or modify a host.

This step is rather easy to implement, although personally I don’t like .NET configuration and prefer INI-files based one, in this particular scenario I’m choosing the standard one. So, in order to map a collection of HostViewModel items, I’ve added HostConfigurationElement, HostConfigurationElementCollection & MainSection types. While working on this task, I’ve found absolutely fabulous and useful .NET configuration guide, which I recommend to everyone. The guide consists of 3 big parts: Unraveling, Decoding & Cracking the Mysteries of .NET 2.0 Configuration, don’t miss it.

So, having configuration-related types done, I got the ability to move host-related data to application configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="NetMonitor.Main" type="NetMonitor.Configuration.MainSection, NetMonitor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  </configSections>
  <NetMonitor.Main>
    <hosts>
      <host name="Wi-Fi Router" address="192.168.1.1"/>
      <host name="Gate" address="10.1.8.2"/>
      <host name="VPN Server" address="10.0.0.66"/>
      <host name="DNS Server" address="10.0.0.1"/>
      <host name="Google" address="google.com"/>
      <host name="Yahoo" address="yahoo.com"/>
    </hosts>
  </NetMonitor.Main>
</configuration>

After the application has been made configurable, it might become useful not only for me, but for somebody else. Therefore, I’ve built it and uploaded here for those who might be interested in using it. In this public version I removed personal hosts such as wi-fi router, gate, etc., because they don’t make any sense to anyone, except me.

Next time I’m going to make some changes to the UI, as there are tons of tasks to be done to get really useful piece of software, even to this tiny one.

Thursday, January 6, 2011

NetMonitor. Bringing together UI and services

In the previous part I introduced a Ping service which purpose was to execute Ping command asynchronously, grab results and notify its listeners. Today I’m going to bring together the UI that had been constructed earlier with this service.

First of all, let’s connect a class that represents a Host with an instance of Ping service. Here is how HostViewModel was initially designed:

  public class HostViewModel : ViewModelBase
  {
    private string name;
    private string health;
    private string responseTime;

    public string Name
    {
      get { return name; }
      set {
        name = value;
        RaisePropertyChanged("Name");
      }
    }

    public string Address { // The same logic }

    public string Health {  // The same logic }

    public string ResponseTime {  // The same logic }
  }

I’m adding a reference to an instance of Ping service in its constructor, an event listener and 2 methods to control host monitoring from outside:

  public class HostViewModel : ViewModelBase
  {
    // ...
    private readonly PingService ping;
    private readonly Queue messageLog;

    public void StartMonitor()
    {
      ping.Start();
    }

    public void StopMonitor()
    {
      ping.Stop();
      Invalidate();
    }

    private void PingCompleted(object sender, PingServiceMessage e)
    {
      if (e.Status != IPStatus.Success) {
        Health = e.ErrorMessage;
        Invalidate();
        return;
      }

      ResponseTime = GetAverageResponseTime(e).ToString("F1");
      Health = "OK";
    }

    private double GetAverageResponseTime(PingServiceMessage e)
    {
      messageLog.Enqueue(e);
      if (messageLog.Count > 5)
        messageLog.Dequeue();

      return messageLog.Select(m => m.RoundtripTime).Average();
    }

    private void Invalidate()
    {
      messageLog.Clear();
    }

    public HostViewModel(string address, string name)
    {
      Address = address;
      Name = name;
      messageLog = new Queue(8);
      ping = new PingService(Address);
      ping.PingCompleted += PingCompleted;
    }
  }
}

Note that I’ve also added a message queue to calculate average value of response time based on last 5 messages. This might help to avoid dramatic raises and falls of the variable.

The second step is to modify MainViewModel in order to initialize a collection of HostViewModels and bind commands to methods.

  public class MainViewModel : ViewModelBase
  {
    // ...
    private bool isMonitoring;

    public ICommand StartCommand
    {
      get {
        if (startCommand == null)
          startCommand = new RelayCommand(Start, () => !IsMonitoring);  // Binding command to methods
        return startCommand;
      }
    }

    public ICommand StopCommand
    {
      get {
        if (stopCommand == null)
          stopCommand = new RelayCommand(Stop, () => IsMonitoring);  // Binding command to methods
        return stopCommand;
      }
    }

    public bool IsMonitoring
    {
      get { return isMonitoring; }
      set {
        if (isMonitoring == value)
          return;

        isMonitoring = value;
        RaisePropertyChanged("IsMonitoring");
      }
    }

    // Starting all monitors
    private void Start()
    {
      IsMonitoring = true;
      foreach (var host in Hosts)
        host.StartMonitor();
    }

    // Stopping monitors
    private void Stop()
    {
      foreach (var host in Hosts)
        host.StopMonitor();
      IsMonitoring = false;
    }

    // Initializing a collection of hosts
    public MainViewModel()
    {
      Hosts = new ObservableCollection
        {
          new HostViewModel("192.168.1.1", "Wi-Fi Router"),
          new HostViewModel("10.1.8.2", "Gate"),
          new HostViewModel("10.0.0.66", "VPN Server"),
          new HostViewModel("10.0.0.1", "DNS Server"),
          new HostViewModel("google.com", "Google")
        };
    }

    public override void Cleanup()
    {
        // Clean up if needed
      if (IsMonitoring)
        Stop();

      base.Cleanup();
    }
  }

That’s it.
Note that in this step as well as in the previous one we don’t touch UI part at all. Nevertheless, the architecture of the application where WPF binding & commands are used, allows us to build an application where  ViewModels don’t know about UI, and Models don’t know about both ViewModels and UI. Good layered architecture, I’d say.

Here is the application. It works just fine, reacts on toolbar buttons’ clicks, refreshing the UI asynchronously without any freezing, using the resources of system ThreadPool:

NetMonitor

As usual, I’ve pushed the modifications to the public repository.

In the next post I’ll make the list of hosts configurable from outside the application code.

Monday, January 3, 2011

NetMonitor. Making Ping service

Earlier, I created a UI prototype for the NetMonitor application, which from my point of view is good enough to do what it is intended to, according to the requirements. Now it is time to add some brains to this piece of WPF.
So, I have to create a service that will execute Ping command to the specified host in a background thread and return a message with Ping command results. As I don’t really need to ping the desired host thousand times per second, the service should be taught to take a break for a while between requests.
What information will I need from the service? I guess, the following will be enough:
  public class PingServiceMessage : EventArgs
  {
    public string ErrorMessage { get; private set; }

    public long RoundtripTime { get; private set; }

    public IPStatus Status { get; private set; }
  }
The service will have the simplest API one've ever seen:
  public class PingService : IDisposable
  {
    public event EventHandler<PingServiceMessage> PingCompleted;

    public void Start() {}

    public void Stop() {}

    public void Dispose() {}
  }
What about the asynchronous implementation? The straightforward solution would be the usage of BackgroundWorker class per Ping service. It would take a delegate, execute it and return a result and then would sleep for a while without releasing its thread. Alex Ilyin & Alexander Nickolaev, my colleagues, would be indignant with such a solution. And you know, they would be definitely right. The solution is obviously resource consuming and is not robust.
A good alternative is System.Threading.Timer class that provides a developer with the possibility to run a given method in a thread, temporarily taken from the ThreadPool. In this case a thread is being taken only for operation execution time and is released immediately after the operation is finished. This strategy is less resource consuming and more stable, because whenever an operation fails, the service won’t suffer and another thread will be taken next time.
So, in order not to be beaten by my vigilant colleagues I’ve chosen the second option and implemented the service with the timer component. The entire implementation could be found in the public repository, I’ve only taken part of it:
  public class PingService : IDisposable
  {
    private readonly string target;
    private readonly Ping ping;
    private Timer timer;

    public event EventHandler<PingServiceMessage> PingCompleted;

    public void Start()
    {
      timer = new Timer(Run, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2));
    }

    public void Stop()
    {
      if (timer != null)
        timer.Dispose();
    }

    private void Run(object state)
    {
      PingServiceMessage message;
      try {
        var result = ping.Send(target, 1000);
        message = new PingServiceMessage(result);
      }
      catch (PingException e) {
        message = new PingServiceMessage(e.Message);
      }
      if (PingCompleted != null) {
        PingCompleted(this, message);
      }
    }

    public PingService(string target)
    {
      this.target = target;
      ping = new Ping();
    }
  }
What is interesting here? Timer is created only in Start method. It is given the ‘Run’ method and is told to execute it every 2 seconds and wait for 1 second before the first time. Instance of Ping class is created in PingService constructor. It can be easily reused with a sequence of ping commands providing that Ping command timeout will be less than timer value. In my case, the timeout is set to 1000 ms, while timer value is set to 2 seconds. Seems to be OK. As there is no legal way to stop the timer other than call ‘Dispose’ on it, the ‘Stop’ method does exactly that. ‘Run’ method tries to execute Ping command with timeout set to 1000 ms and catches PingExceptions, if any. Finally, it notifies PingService listeners by sending a new instance of PingServiceMessage that might contain information about Ping command result or an exception message.

Let’s check whether the service works. To do so, I’ve added a simple console application and used the service there:
  class Program
  {
    static void Main(string[] args)
    {
      var service = new PingService("192.168.1.1");
      service.PingCompleted += PingCompleted;
      service.Start();
      Console.ReadKey();
    }

    static void PingCompleted(object sender, PingServiceMessage e)
    {
      var service = sender as PingService;
      Console.WriteLine(string.Format("Reply from {0}: time={1} ms", service.Target, e.RoundtripTime));
    }
  }
Here is the result:
NetMonitorConsole
Wow, works like a charm! =)
In the next posts I will try connecting all these parts together.