In previous articles, we have explored using the Google Chart APIs to integrate static charts and graphs with iControl and iRules to build monitoring applications.

In this article, I’m going to move beyond the static charting APIs and take a look at the more complex Google Visualization API.  It is a JavaScript library that allows you to embed interactive charts, graphs, or other graphics onto a webpage.

For this application, I will take a look at the CPU metrics on a BIG-IP and build a dynamic gauge based dashboard.

The Application

CPUVisualization_2

There are several approaches to building a dashboard and for this example, I've build a Windows Form application in C#.  The core of the app is the WebBrowser control which essentially wraps the Internet Explorer COM control up in a nice little package.  I then use the iControl Assembly to get the metrics from the BIG-IP, and then dynamically execute JavaScript functions from the C# code to manipulate the gauges.

The main components to the application are described in the following sections.

Connecting to the BIG-IP

CPUVisualization_1I use a iControl.Dialogs.ConnectionDialog, available from the iControl Assembly, to connect to the BIG-IP.  If you use the iRule Editor, this may look familiar…  The DoConnect() method will attempt to connect to the BIG-IP with the ConnectionDialog and, if it succeeds, then build the initial browser content for the WebBrowser control.  It then updates the charts with the latest data and then triggers the timer to start a update cycle.  Each time the timer triggers, another data collection is made and the charts are updated with the updateCharts() method.

   1: private void DoConnect()
   2: {
   3:     if (timer1.Enabled)
   4:     {
   5:         timer1.Enabled = false;
   6:     }
   7:     else
   8:     {
   9:         DialogResult dr = _cd.ShowDialog();
  10:         if (dr == DialogResult.OK)
  11:         {
  12:             loadBrowserContents(buildHTMLContent());
  13:             updateCharts();
  14:             timer1.Enabled = true;
  15:         }
  16:     }
  17:     updateMenus();
  18: }

Creating HTML Page

The first time a connection is made to the LTM, the buildHTMLContent() method is called which makes a call to the get_cpu_usage_extended_information() iControl method to extract the CPU metrics for the given BIG-IP.  In this example, I’m only using the first host.  With future versions of BIG-IP, you will be able to manage multiple “hosts” within a single BIG-IP system.

An HTML page is created with DIV elements created and named for each CPU.  A row in the output table is created for each CPU with 3 gauges for Usage, Speed, and Temperature.

   1: private String buildHTMLContent()
   2: {
   3:     String page_data = "";
   4:  
   5:     if (_cd.m_interfaces.initialized)
   6:     {
   7:         iControl.SystemCPUUsageExtendedInformation cpu_info =
   8:             _cd.m_interfaces.SystemSystemInfo.get_cpu_usage_extended_information(new String[] { "0" });
   9:  
  10:         iControl.SystemCPUUsageExtended host_info = cpu_info.hosts[0];
  11:         String host_id = host_info.host_id;
  12:         iControl.CommonStatistic[][] statisticAofA = host_info.statistics;
  13:  
  14:         String table_rows = "";
  15:  
  16:         // Loop over CPUs
  17:         for (int i = 0; i < statisticAofA.Length; i++)
  18:         {
  19:             long cpu_id = extractStatistic(statisticAofA[i], iControl.CommonStatisticType.STATISTIC_CPU_INFO_CPU_ID);
  20:  
  21:             table_rows += "<tr><td>C<br/>P<br/>U<br/>" + cpu_id + "</td>";
  22:             table_rows += "<td class='gauge'><div id='gauge_" + cpu_id + "_0'></div></td>";
  23:             table_rows += "<td class='gauge'><div id='gauge_" + cpu_id + "_1'></div></td>";
  24:             table_rows += "<td class='gauge'><div id='gauge_" + cpu_id + "_2'></div></td></tr>";
  25:  
  26:         }
  27:         updateTimestamp(true);
  28:         page_data = @"<html><head><title>iControl CPU Gauges</title>
  29:             <style type='text/css'>
  30:             body,td,th { font-family: Tahoma; font-size: 10pt; }
  31:             .gaugex { width:200px;height:200px; }
  32:             </style>
  33:             <script type='text/javascript' src='https://www.google.com/jsapi'></script>
  34:             <script type='text/javascript'>
  35:             google.load('visualization', '1', {packages:['gauge']});
  36:             </script>
  37:             </head>";
  38:  
  39:         page_data += "<body><center><table border='0'>";
  40:         page_data += table_rows;
  41:         page_data += "</table></center></body>";
  42:         page_data += @"<script type='text/javascript'>
  43:             var chartWidth = 200;
  44:             var chartHeight = 200;
  45:  
  46:             function setChartSize(w, h) {
  47:                 chartWidth=w;
  48:                 chartHeight=h;
  49:             }
  50:             function drawChart(id, chart_num, label, value) {
  51:                 var data = new google.visualization.DataTable();
  52:                 data.addColumn('string', 'Label');
  53:                 data.addColumn('number', 'Value');
  54:                 data.addRows(1);
  55:                 data.setValue(0, 0, label);
  56:                 data.setValue(0, 1, parseInt(value));
  57:  
  58:                 var elem = document.getElementById(id);
  59:                 var chart = new google.visualization.Gauge(elem);
  60:  
  61:                 var usage_options = {
  62:                     width:chartWidth,height:chartHeight,
  63:                     min:0,max:100,greenFrom:70,greenTo:75,yellowFrom:75,yellowTo:90,redFrom:90,redTo:100,
  64:                     minorTicks:5,
  65:                     majorTicks:['0','10','20','30','40','50','60','70','80','90','100']
  66:                     };
  67:                 var fanspeed_options = {
  68:                     width:chartWidth,height:chartHeight,
  69:                     min:0,max:6000,
  70:                     greenFrom:4000,greenTo:4500,yellowFrom:4500,yellowTo:5500,redFrom:5500,redTo:6000,
  71:                     minorTicks:5,
  72:                     majorTicks:['0','1000','2000','3000','4000','5000','6000']
  73:                    };
  74:                 var temp_options = {
  75:                     width:chartWidth,height:chartHeight,
  76:                     min:0,max:100,greenFrom:70,greenTo:75,yellowFrom:75,yellowTo:90,redFrom:90,redTo:100,
  77:                     minorTicks:5,
  78:                     majorTicks:['0','10','20','30','40','50','60','70','80','90','100']
  79:                     };
  80:                 var options = '';
  81:                 if ( chart_num == 0 ) { options = usage_options; }
  82:                 else if ( chart_num == 1 ) { options = fanspeed_options; }
  83:                 else if ( chart_num == 2 ) { options = temp_options; }
  84:  
  85:                 chart.draw(data, options);  
  86:             }
  87:             </script></html>";
  88:     }
  89:  
  90:     return page_data;
  91: }

Inserting HTML Page in Browser Control

Injecting the HTML contents into the WebBrowser control is as simple as assigning it’s DocumentText property with the HTML contents.

   1: private void loadBrowserContents(String s)
   2: {
   3:     if ( null != s )
   4:     {
   5:         webBrowser1.DocumentText = s;
   6:     }
   7: }

Refreshing Charts

Once the WebBrowser control contains the contents of the page with the embedded functions to update the gauges, the updateCharts() method in this example will query the cpu metrics and make calls into the embedded JavaScript on the page with the WebBrowser’s Document.InvokeScript() method.

The updateChart() method takes as input the id, cpu id, title, and value for the chart.  These values are then bundled up into an Object array and passed to the drawChart() JavaScript function with the parameters.  The drawChart() JavaScript function then calls the Google Visualization chart object’s draw() method with the supplied options and the chart is then refreshed.

   1: private void updateCharts()
   2: {
   3:     if (_cd.m_interfaces.initialized)
   4:     {
   5:         DoResize();
   6:  
   7:         iControl.SystemCPUUsageExtendedInformation cpu_info =
   8:             _cd.m_interfaces.SystemSystemInfo.get_cpu_usage_extended_information(new String[] { "0" });
   9:  
  10:         iControl.SystemCPUUsageExtended host_info = cpu_info.hosts[0];
  11:         String host_id = host_info.host_id;
  12:         iControl.CommonStatistic[][] statisticAofA = host_info.statistics;
  13:  
  14:         iControl.SystemPlatformCPUs PlatformCPUs =
  15:             _cd.m_interfaces.SystemSystemInfo.get_cpu_metrics();
  16:  
  17:         // Loop over CPUs
  18:         for (int i = 0; i < statisticAofA.Length; i++)
  19:         {
  20:             iControl.SystemCPUMetric[] CPUMetricA = PlatformCPUs.cpus[i];
  21:  
  22:             long cpu_index = extractMetric(CPUMetricA, iControl.SystemCPUMetricType.CPU_INDEX);
  23:             long cpu_temp = extractMetric(CPUMetricA, iControl.SystemCPUMetricType.CPU_TEMPERATURE);
  24:             long cpu_fan_speed = extractMetric(CPUMetricA, iControl.SystemCPUMetricType.CPU_FAN_SPEED);
  25:  
  26:             long cpu_id = extractStatistic(statisticAofA[i], iControl.CommonStatisticType.STATISTIC_CPU_INFO_CPU_ID);
  27:             long cpu_usage_ratio = extractStatistic(statisticAofA[i], iControl.CommonStatisticType.STATISTIC_CPU_INFO_USAGE_RATIO);
  28:  
  29:             updateChart("gauge_" + cpu_id + "_0", 0, "Usage", cpu_usage_ratio);
  30:             updateChart("gauge_" + cpu_id + "_1", 1, "Speed", cpu_fan_speed);
  31:             updateChart("gauge_" + cpu_id + "_2", 2, "Temp", cpu_temp);
  32:  
  33:         }
  34:         updateTimestamp(true);
  35:     }
  36: }
  37:  
  38: private void updateChart(String id, long cpuid, String title, long value)
  39: {
  40:     if (null != webBrowser1.Document)
  41:     {
  42:         String[] args = new String[] { id, cpuid.ToString(), title, value.ToString() };
  43:         webBrowser1.Document.InvokeScript("drawChart", args);
  44:     }
  45: }

Resizing Charts

Resizing the charts in the page was rather trivial.  I created another embedded JavaScript function setChartSize() which takes as parameters the chart’s width and height.  The next time the drawChart() method is called, it uses these values for the creation of the charts.

   1: private void DoResize()
   2: {
   3:     if (timer1.Enabled)
   4:     {
   5:         // Calculate the new chart sizes;
   6:         long width = webBrowser1.Width;
   7:         long height = webBrowser1.Height;
   8:  
   9:         long newWidth = ((width) / 3) - 50;
  10:         long newHeight = ((height) / 2) - 20;
  11:  
  12:         if (newHeight > newWidth) { newHeight = newWidth; }
  13:         if (newWidth > newHeight) { newWidth = newHeight; }
  14:  
  15:         resizeCharts(newWidth, newHeight);
  16:     }
  17: }
  18: private void resizeCharts(long width, long height)
  19: {
  20:     if (null != webBrowser1.Document)
  21:     {
  22:         String [] args = new String[] { width.ToString(), height.ToString() };
  23:         webBrowser1.Document.InvokeScript("setChartSize", args);
  24:     }
  25: }

Video Walkthrough

 

 

Get The Source

The source code for this application can be downloaded from the following link: CPUVisualization.zip

Related Articles on DevCentral