Click here to Skip to main content
15,897,181 members
Articles / Web Development / ASP.NET

AJAX-enabled Performance Counter Web Control

Rate me:
Please Sign up or sign in to vote.
4.66/5 (14 votes)
4 Mar 2007BSD13 min read 104.4K   1.2K   75  
ASP.NET web control that renders performance counter data to the screen in the form of a text label, progress bar, histogram, or line graph and automatically updates itself via an AJAX call
Type.registerNamespace("Stratman.Web.UI");

/// <summary>
/// Display method to use when rendering a performance counter's data on screen.
/// </summary>
Stratman.Web.UI.PerformanceCounterDisplayType = function() {};
Stratman.Web.UI.PerformanceCounterDisplayType.prototype =
{
    /// <summary>
    /// Display the current value of the counter.
    /// </summary>
    Text: 0,
    /// <summary>
    /// Display a single, updating bar representing the percentage value of the counter between
    /// its floor and ceiling.
    ProgressBar: 1,
    /// <summary>
    /// Display a line graph of the counter's historical data.
    /// </summary>
    LineGraph: 2,
    /// <summary>
    /// Display a histogram (bar graph) of the counter's historical data.
    /// </summary>
    Histogram: 3
}

/// <summary>
/// Specifies the direction in which items of a list control are displayed.
/// </summary>
Stratman.Web.UI.PerformanceCounterDisplayType.registerEnum("Stratman.Web.UI.PerformanceCounterDisplayType");

Sys.UI.RepeatDirection = function() {};
Sys.UI.RepeatDirection.prototype =
{
    /// <summary>
    /// Items of a list are displayed horizontally in rows from left to right, then top to bottom,
    /// until all items are rendered.
    /// </summary>
    Horizontal: 0,
    /// <summary>
    /// Items of a list are displayed vertically in columns from top to bottom, and then left to 
    /// right, until all items are rendered.
    /// </summary>
    Vertical: 1
}

Sys.UI.RepeatDirection.registerEnum("Sys.UI.RepeatDirection");

var refreshHash = new Object();

/// <summary>
/// Constructor for the client-side class that holds information about a performance counter 
/// control.
/// </summary>
/// <param name="id">
/// Client ID of the control.
/// </param>
/// <param name="type">
/// Display method to use when rendering the control's data.
/// </param>
/// <param name="value">
/// Initial value for the performance counter.
/// </param>
/// <param name="refreshInterval">
/// Interval, in seconds, between data refresh attempts.
/// </param>
/// <param name="width">
/// Width of the rendered data on screen (not applicable for text display type).
/// </param>
/// <param name="height">
/// Height of the rendered data on screen (not applicable for text display type).
/// </param>
/// <param name="ceiling">
/// Maximum value of this performance counter.
/// </param>
/// <param name="floor">
/// Minimum value of this performance counter.
/// </param>
/// <param name="formatString">
/// JavaScript-compatible format string (String.format()) used to format the value prior to
/// rendering it on screen (applicable only for text display type).
/// </param>
/// <param name="orientation">
/// Direction in which the progress bar should "grow" for progress bar display types.
/// </param>
/// <param name="historyCount">
/// Number of historical values that we are to display for the performance counter (applicable only
/// for histogram and line graph display types).
/// </param>
/// <param name="cssClass">
/// CSS class to apply to the HTML element(s) rendered on the screen.
/// </param>
/// <param name="categoryName">
/// Category name of the counter that we are to render.
/// </param>
/// <param name="counterName">
/// Name of the counter that we are to render.
/// </param>
/// <param name="instanceName">
/// Instance name of the counter that we are to render.
/// </param>
/// <param name="machineName">
/// Name of the machine on which the performance counter resides.
/// </param>
/// <param name="onChange">
/// Client-side JavaScript function that we should call when this control's value is refreshed.
/// </param>
Stratman.Web.UI.PerformanceCounter = function(id, type, value, refreshInterval, width, height, ceiling, floor, formatString, orientation, historyCount, cssClass, categoryName, counterName, instanceName, machineName, onChange)
{
    // Initialize the members of the class
    this.ID = id;
    this.Type = type;
    this.Value = value;
    this.RefreshInterval = refreshInterval;
    this.Width = width;
    this.Height = height;
    this.Ceiling = ceiling;
    this.Floor = floor;
    this.FormatString = formatString;
    this.Orientation = orientation;
    this.HistoryCount = historyCount;
    this.CssClass = cssClass;
    this.CategoryName = categoryName;
    this.CounterName = counterName;
    this.InstanceName = instanceName;
    this.MachineName = machineName;
    this.OnChange = onChange;
    this.ChildElementIDs = [];
    
    // Any arguments passed to us after the formal arguments represent the client IDs of child
    // elements contained within this control
    for (var i = 17; i < arguments.length; i++)
        Array.add(this.ChildElementIDs, arguments[i]);
    
    // Perform the initial String.format() on the counter's value for text display controls
    if (type == Stratman.Web.UI.PerformanceCounterDisplayType.Text)
        this.Render();

    // Initialize the historical samples for line graphs and histograms, which require historical
    // data        
    else if (type == Stratman.Web.UI.PerformanceCounterDisplayType.LineGraph || type == Stratman.Web.UI.PerformanceCounterDisplayType.Histogram)
    {
        this.HistorySamples = [];
        
        for (var i = 0; i < historyCount - 1; i++)
            this.HistorySamples[i] = floor;
            
        this.HistorySamples[historyCount - 1] = value;
        
        // For line graphs, initialize the vector graphics library and perform the initial render
        if (type == Stratman.Web.UI.PerformanceCounterDisplayType.LineGraph)
        {
            this.VectorGraphics = new jsGraphics(this.ChildElementIDs[0]);
            this.Render();
        }
    }
    
    // Register the counter for periodic refreshes
    if (refreshInterval > 0)
        RegisterForRefresh(id, refreshInterval);
}

/// <summary>
/// Updates the CSS class being used for the control.
/// </summary>
/// <param name="cssClass">
/// New CSS class to use for this control.
/// </param>
Stratman.Web.UI.PerformanceCounter.prototype.SetCssClass = function(cssClass)
{
    this.CssClass = cssClass;

    // For text and progress bar controls, just update the class of the single child element
    if (this.Type == Stratman.Web.UI.PerformanceCounterDisplayType.Text || this.Type == Stratman.Web.UI.PerformanceCounterDisplayType.ProgressBar)
        document.getElementById(this.ChildElementIDs[0]).className = cssClass;
    
    // For histograms, update the class of each bar
    else if (this.Type == Stratman.Web.UI.PerformanceCounterDisplayType.Histogram)
    {
        for (var i = 0; i < this.ChildElementIDs.length; i++)
            document.getElementById(this.ChildElementIDs[i]).className = cssClass;
    }
    
    // For line graphs, call Render() so that the vector graphics library can render the graph
    else if (this.Type == Stratman.Web.UI.PerformanceCounterDisplayType.LineGraph)
        this.Render();
}

/// <summary>
/// Renders the performance counter's data to the screen using the pre-determined display method.
/// </summary>
Stratman.Web.UI.PerformanceCounter.prototype.Render = function()
{
    // For text displays, simply call String.format()
    if (this.Type == Stratman.Web.UI.PerformanceCounterDisplayType.Text)
        document.getElementById(this.ChildElementIDs[0]).innerHTML = String.format(this.FormatString, this.Value);
    
    // For progress bars, just set the width or height (depending on the orientation) of the 
    // progress bar
    else if (this.Type == Stratman.Web.UI.PerformanceCounterDisplayType.ProgressBar)
    {
        if (this.Orientation == Sys.UI.RepeatDirection.Vertical)
            document.getElementById(this.ChildElementIDs[0]).style.height = Math.round(Math.max(Math.min((this.Value - this.Floor) / this.Ceiling, 1) * this.Height, 1)) + "px";
        
        else
            document.getElementById(this.ChildElementIDs[0]).style.width = Math.round(Math.max(Math.min((this.Value - this.Floor) / this.Ceiling, 1) * this.Width, 1)) + "px";
    }
        
    // For histograms, set the height of each bar to the value of the corresponding entry in the
    // history data
    else if (this.Type == Stratman.Web.UI.PerformanceCounterDisplayType.Histogram)
    {
        for (var i = 0; i < this.HistoryCount; i++)
            document.getElementById(this.ChildElementIDs[i]).style.height = Math.max(Math.min((this.HistorySamples[i] - this.Floor) / this.Ceiling, 1) * this.Height, 1) + "px";
    }
    
    // For line graphs, call the drawLine() function in the vector graphics library for each entry
    // in the history data
    else if (this.Type == Stratman.Web.UI.PerformanceCounterDisplayType.LineGraph)
    {
        var sampleWidth = Math.round(this.Width / this.HistoryCount);
        var lineHTML = "";
        
        this.VectorGraphics.setCssClass(this.CssClass);
        this.VectorGraphics.clear();
        
        for (var i = 0; i < this.HistoryCount - 1; i++)
            this.VectorGraphics.drawLine((i * sampleWidth), this.Height - Math.round(Math.min((this.HistorySamples[i] - this.Floor) / this.Ceiling, 1) * (this.Height - 1)) - 1, ((i + 1) * sampleWidth), this.Height - Math.round(Math.min((this.HistorySamples[i + 1] - this.Floor) / this.Ceiling, 1) * (this.Height - 1)) - 1);
            
        this.VectorGraphics.paint();
    }
}

/// <summary>
/// Registers a control ID for data refresh at a pre-determined interval; we group all the controls
/// with the same refresh interval together so that we can consolidate the callbacks we make to
/// the web server.
/// </summary>
/// <param name="id">
/// ID of the control that we are to register.
/// </param>
/// <param name="refreshInterval">
/// Interval, in seconds, between data refresh attempts for this control.
/// </param>
function RegisterForRefresh(id, refreshInterval)
{
    // Create the refresh hash entry for this interval if it doesn't already exist and make a call
    // to setTimeout() to initialize the callback
    if (refreshHash[refreshInterval] == null)
    {
        refreshHash[refreshInterval] = [];
        window.setTimeout("UpdatePerformanceCounters(" + refreshInterval + ")", refreshInterval * 1000);
    }
    
    Array.add(refreshHash[refreshInterval], id);
}

/// <summary>
/// Makes a callback to the web server to get the updated values for all performance counter 
/// controls scheduled to be refreshed at the given interval.
/// </summary>
/// <param name="refreshInterval">
/// Interval that we are refreshing.
/// </param>
function UpdatePerformanceCounters(refreshInterval)
{
    // Assemble the list of control IDs to update into a comma-delimited string
    var performanceCounterIDs = refreshHash[refreshInterval][0];

    for (var i = 1; i < refreshHash[refreshInterval].length; i++)
        performanceCounterIDs += "," + refreshHash[refreshInterval][i];

    // Make the callback to the web server and call setTimeout() again to re-register this callback
    WebForm_DoCallback(refreshHash[refreshInterval][0], performanceCounterIDs, RenderPerformanceCounters, refreshInterval, null, false);
    window.setTimeout("UpdatePerformanceCounters(" + refreshInterval + ")", refreshInterval * 1000);
}

/// <summary>
/// Callback function that is invoked when we get a successful response from the web server for a
/// counter value update callback request; updates the values of the applicable controls and re-
/// renders them.
/// </summary>
/// <param name="response">
/// String that was returned from the web server.
/// </param>
/// <param name="context">
/// Context associated with the callback; in this case it's the refresh interval.
/// </param>
function RenderPerformanceCounters(response, context)
{
    var performanceCounterValues = response.split(",");
    
    // Loop through each control that we're to update
    for (var i = 0; i < performanceCounterValues.length; i++)
    {
        var performanceCounter = performanceCounters[refreshHash[context][i]];
        performanceCounter.Value = Number.parseInvariant(performanceCounterValues[i]);
        
        // Update the history data for line graphs and histograms
        if (performanceCounter.Type == Stratman.Web.UI.PerformanceCounterDisplayType.LineGraph || performanceCounter.Type == Stratman.Web.UI.PerformanceCounterDisplayType.Histogram)
        {
            for (var x = 0; x < performanceCounter.HistoryCount - 1; x++)
                performanceCounter.HistorySamples[x] = performanceCounter.HistorySamples[x + 1];
        
            performanceCounter.HistorySamples[performanceCounter.HistoryCount - 1] = performanceCounter.Value;
        }
        
        // Render the control
        performanceCounter.Render();
        
        // Invoke the value changed event handler, if it's set
        if (performanceCounter.OnChange)
            performanceCounter.OnChange(performanceCounter);
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Software Developer (Senior) AOC Solutions
United States United States
I'm a software architect in the truest sense of the word: I love writing code, designing systems, and solving tough problems in elegant ways. I got into this industry not for the money, but because I honestly can't imagine myself doing anything else. Over my career, I've worked on just about every major Microsoft web technology, running the gamut from SQL Server 7.0 to 2008, from .NET 1.1 to 4.0, and many others. I've made a name for myself and have risen to my current position by being able to visualize and code complex systems, all while keeping the code performant and extensible.

Both inside and outside of work, I love researching new technologies and using them sensibly to solve problems, not simply trying to force the newest square peg into the round hole. From emerging Microsoft projects like AppFabric to third party or open source efforts like MongoDB, nServiceBus, or ZeroMQ, I'm constantly trying to find the next technology that can eliminate single points of failure or help scale my data tier.

Outside of work, I'm a rabid DC sports fan and I love the outdoors, especially when I get a chance to hike or kayak.

Comments and Discussions