A System-monitoring Application
There's an old tool I've had on my desktop for many years. It was originally called NetMedic, and I think I paid $50 for it back in, oh, about 1997. At some point it was sold, rebranded as VitalAgentIT and given away for free. Then it was built into some expensive, high-end IT solution and never heard from again. Here's the classic UI, still one of the nicest Windows apps ever.
VitalAgentIT displaying a connection to the MSFT update site.
Without this neat app, I always feel a little blind. It tells you exactly what kind of traffic is moving over your network in a slick, easy-to-grasp user interface.
I've been installing this puppy since NT4.0 days. It has some occasional startup problems with XP Pro, but overall, it's held up remarkably well.
Until XP x64 Edition. It won't run on my HP xw8200 workstation, and I won't really expect it to fair better on Vista, so it's getting to be the end of the road for this app. But it's just so useful, and I haven't found an inexpensive solution to replace it. So it's time to write my own.
Ideally, I'd like to write an almost exact replica of the original NetMedic app. WPF provides an ideal presentation stack to mimic arbitrary UI, so this makes a good project for learning WPF. It would be really neat if I could apply a "NetMedic" style, as well as other cool styles like USS Enterprise control panels. To pick the geekiest possible example.
NetMedic/VitalAgentIT shares similarities with another system monitoring app, Norton System Doctor.
Norton System Doctor
Both programs are built on Win32 APIs. NetMedic is (probably) built on NetMon, and Norton System Doctor is built on the performance monitor API (PerfMon).
Since there might be several different APIs for monitoring, I wanted to abstract the data source. I whipped up the generic ISignalGenerator interface.
public interface ISignalGenerator<T>
{
void Start();
void Stop();
double SampleRate { get; }
T Seed { get; }
long StartSample { get; }
double Gain { get; set; }
double DCOffset { get; set; }
double Frequency { get; set; }
event NewSamplesEventHandler<T> NewSamples;
}
The Start method turns on the firehose, and the performance monitor samples the underlying data stream every SampleRate milliseconds. Data samples are returned through the NewSamples event. The underlying implementation is assumed to be asynchronous.
The .NET Framework provides the convenient PerformanceCounter wrapper type around the Win32 implementation. This type lives in the System.Diagnostics namespace.
I wanted to abstract the PerformanceCounter type a bit, and I wanted to give it a buffer for its data stream. The IPerformanceMonitor interface defines the controller part of the Model-View-Controller pattern.
namespace
SystemMonitorLib.PerformanceMonitors
{
public interface IPerformanceMonitor : INotifyPropertyChanged
{
string CounterName { get; }
string CounterCategory { get; }
string InstanceName { get; }
string MachineName { get; }
float[] DisplayBuffer { get; }
int DisplayBufferSize { get; set; }
float MinValue { get; }
float MaxValue { get; }
double Gain { get; set; }
void Start();
void Stop();
}
}
The model is implemented by a type that implements the ISignalGenerator interface. The view is represented by the generic ISignalView interface.
namespace
SystemMonitorLib
{
public interface ISignalView<T> : INotifyPropertyChanged
{
ISignalGenerator<T> SignalSource { get; }
T[] DisplayBuffer {
get; }
int DisplayBufferSize { get; set; }
T MinValue {
get; }
T MaxValue {
get; }
}
}
The client view of the data is provided by the DisplayBuffer property. Clients can set the length of the buffer dynamically by setting the DisplayBufferSize property. The MinValue and MaxValue properties return the minimum and maximum values produced by the data stream so far. This enables scaling scenarios for clients. I won't bore you any further with design details. The fun stuff is in the WPF parts.
My design goal was to produce a pluggable, extensible architecture of controls that are also designer-friendly. For designability, you can't beat a composite control, aka UserControl. Windows Forms and WPF both have UserControl implementations, which give you a great deal of functionality out of the box: message routing for keyboard and mouse, tab order for child controls, focus, etc.
My PerformanceMonitorControl type derives from System.Windows.Controls.UserControl. As far as layout is concerned, there isn't much to it.
<
UserControl x:Class="PerformanceMonitorControlLib.PerformanceMonitorControl"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
<
DockPanel>
<
DockPanel.DataContext>
<
Binding Source=""/>
</
DockPanel.DataContext>
<
Canvas Name="_signalCanvas" />
<
Label Name="_perfCounterName" DockPanel.Dock="Bottom" Foreground="White" />
</
DockPanel>
</
UserControl>
Ideally, I want to be able to drop individual PerformanceMonitorControl instances onto a WPF page from the Cider Toolbox, then set their properties in Cider's property grid. But we're not quite there yet, so here's some hand-tooled code.
<
Window x:Class="SystemMedic.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="SystemMedic" Height="300" Width="300"
xmlns:pmc="clr-namespace:PerformanceMonitorControlLib;assembly=PerformanceMonitorControlLib"
Loaded="WindowLoaded"
>
<
Grid Margin="1" >
<
Grid.RowDefinitions>
<
RowDefinition/>
<
RowDefinition/>
<
RowDefinition/>
<
RowDefinition/>
<
RowDefinition/>
<
RowDefinition Height="20"/>
</
Grid.RowDefinitions>
<
Grid.ColumnDefinitions>
<
ColumnDefinition/>
<
ColumnDefinition/>
</
Grid.ColumnDefinitions>
<
pmc:PerformanceMonitorControl Name="perfMonControl" AutoScale="true" TraceStroke="Black" TraceStrokeThickness=".5" Background="Gray" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Black" BorderThickness="1" />
<
pmc:PerformanceMonitorControl Name="perfMonControl2" AutoScale="true" TraceStroke="Cyan" Background="Black" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Black" BorderThickness="1" />
<
pmc:PerformanceMonitorControl Name="perfMonControl3" AutoScale="true" TraceStroke="LightSkyblue" Background="Black" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Black" BorderThickness="1" />
<
DockPanelGrid.Row="3"Grid.Column="0"Grid.ColumnSpan="2"Background="Black"LastChildFill="true" >
<
pmc:PerfMonDigitalControlDockPanel.Dock="Bottom"Name="perfMonDigitalControl2"Annotations="false"Background="Black"BorderBrush="Black"BorderThickness="1" />
<
pmc:PerformanceMonitorControlName="perfMonControl4"AutoScale="true"TraceStroke="Coral"Background="Black"Grid.Row="3"BorderBrush="Black"BorderThickness="1" />
</
DockPanel>
<pmc:PerfMonDigitalControl Name="perfMonDigitalControl1" Background="Black" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Black" BorderThickness="1" />
<Button Name="btnStart" Grid.Row="5" Grid.Column="0" Background="Gray" BorderBrush="Black" BorderThickness="1" Click="OnClickStart">Start</Button>
<Button Name="btnStop" Grid.Row="5" Grid.Column="1" Background="Gray" BorderBrush="Black" BorderThickness="1" Click="OnClickStop">Stop</Button>
</Grid>
</Window>
The neat thing about this code is that I was able to compose two different PerformanceMonitorControl instances (one a numeric display, the other a signal trace) to form a new display (bolded code). Ultimately, all the hard-coded property values will be factored into styles.
Here's what the app looks like. I call it SystemMedic, since it's trying to be a combination of NetMedic and Norton System Doctor.
SystemMedic application displaying performance data with various styling.
There's a lot more I can do with this object model, but this is a start. Ultimately, I want it to look much more similar to NetMedic. It would also be cool to come up with a style that looks like the USS Enterprise displays from, say, Star Trek: The Next Generation. Which is to say, I want to be able to apply arbitrary styling to each control instance. Being able to do that at design time would be the sweetest, and between Sparkle and Cider, it should be quite doable.
Update: By popular demand, I've posted the source code, with the caveat that it's quite prototypical.
Here are a few things you'll need to know:
- I've tested against a recent version of the RC1 bits for WPF. I would expect anything after the June CTP to build and run.
- You'll need to edit BytesSentMonitor.cs and BytesReceivedMonitor.cs to insert the correct string describing your NIC.
- You can install the fonts in SystemMedic\Fonts to get the digital displays to have that cool LED look.
Since this is a prototype that I haven't touched since May, I'll describe a few design details for future work.
In general, the code needs to be refactored to adhere more closely to the Framework Design Guidelines. I would start by removing the interfaces and replacing them with abstract base classes.
I think the framework should be simplified greatly, as well. I originally had in mind a much more general signal-display framework, which could handle fast (kHz) signals, but that's largely unnecessary for this application.
In any case, have fun, and let me know how it works for you.
Comments
- Anonymous
July 25, 2006
Jim,
Super project. Would you be willing to post the code for it?
Thanks - Anonymous
August 21, 2006
Thanks for the props, Tom. Please forgive the tardy reply -- I discovered that I didn't have the email notifications for my blog turned on. D'oh.
If you're still interested, I'll post the code, with the caveat that this is a prototype. - Anonymous
August 22, 2006
Jim,
If you don't mind, could you please post the SystemMedic prototype project?
Thank you - Anonymous
August 22, 2006
Done. Check out http://www.windowsforms.net/blogs/jgalasyn/PerformanceMonitorControlLib.zip. - Anonymous
September 09, 2006
Please please post a compiled binary for non-developers when it's not a prototype/ready for decent running. - Anonymous
May 31, 2009
PingBack from http://woodtvstand.info/story.php?id=5325