The Polymetric Dashboard
Overview of the Polymetric Dashboard
The Polymetric Dashboard is a stand-alone module providing enhanced monitoring capabilities for Caché-based applications. Equipped with over one hundred sensors that monitor key system metrics, a robust REST API, and a modular AngularJS user interface, the Dashboard is fully functional as installed. However, the Dashboard is designed to be customizable; developers can easily create new sensors to monitor any system or application metric as well as tailor the visualization of collected data to their specific needs.
Polymetric Dashboard Architecture
There are three main components of the Dashboard:
  1. The user interface visualizes the collected metric data.
    A default layout is provided, however developers can customize it.
  2. A REST API, written in Caché ObjectScript (COS) using the CSP REST architecture, provides access to the collected metric data.
    The REST API contains 4 generic calls, however, each can be customized and more can be added.
  3. The monitoring infrastructure, based on Caché System Monitor components (also written in COS), defines the sensors and stores the metric data collected.
    The infrastructure includes over one hundred predefined sensors; procedures for creating additional custom sensors are described in Creating User-Defined Sensors.
Key System Monitor Concepts
Sensors are contained within a Caché class and are used to collect data about system metrics. Caché includes over 100 predefined sensors, and you can extend this set by creating your own for the Dashboard. For more information about sensors, see The System Monitor Process section of the “Caché System Monitor” chapter of the Caché Monitoring Guide. A sensor’s state is normal, warning, or alert, depending the recent values of the metric being monitored; see the System Monitor Health State section of the “Caché System Monitor” chapter.
Because the Dashboard’s monitoring infrastructure is based on System Monitor, developers intending to extend the Dashboard should thoroughly review the Caché System Monitor chapter of the Caché Monitoring Guide.
The Polymetric Dashboard User Interface
The user interface (UI) of the Polymetric Dashboard visualizes the collected metric data. This section covers the following UI topics:
UI Tabs
The user interface includes several tabs, each having a specific function. By default the UI has six predefined tabs; however, new tabs can be created and existing removed.
Summary, Performance, and ECP Tabs
The Summary, Performance, and ECP tabs are composed of charts pertaining to the theme of the tab. The charts can be reordered and re-sized, otherwise their functionality is relatively static. These tabs provide examples of a simple type of tab that can easily be added to the Dashboard.
All Sensors Tab
The All Sensors tab contains a list of all the sensors currently registered by the Dashboard and displays their data in numeric and charted form. The list provides a resource to determine what exactly is being monitored as well as information about each of the sensors.
Playground Tab
The Playground tab contains the functionality to build widgets containing any of the visualization tools using the user interface, therefore sensor data can be displayed in any format without writing code. Playground widget configurations can be saved to the browsers local storage as well as exported (and later imported) as JSON strings.
Showcase Tab
The showcase tab provides details on continuing development of the Dashboard and contains live examples, code snippets, and explanations of the visualizations tools provided by the Dashboard.
UI Visualization Tools
There are five default UI visualization tools. They are built using AngularJS element directives and can be customized by changing their element attributes. Additionally, all of the default visualization tools are integrated with the REST API and automatically keep the data they display up-to-date.
Line Chart
The line chart implements NVD3's line chart, displaying a green line when the sensor is in a normal state, a yellow line when the sensor is in a warning state, and a red line with the area below it filled when the sensor is in an alert state. Additionally, numerical data for each point on the line is shown when it is hovered over by the cursor.
Sparkline Chart
The sparkline chart implements NVD3's line chart as well, but in a more condensed format.
Sensor Display
The sensor display is a numerical display that can show all of a sensors properties and data. You can select which of the eleven available metrics to show on a sensor display, as well as the color and width of each metric’s cell on the display.
State Icon
A sensor’s state is depicted by three horizontal bars, the bottom is green in a normal state, the middle is yellow in a warning state, and the top is red if in an alert state. If no sensor state is known, all three bars will be gray.
Metadata Popup
The metadata popup is a custom tooltip that shows a sensor’s properties, including warning value, critical value, and description. When you hover over a charts title, or a row on the All Sensors tab the metadata popup is displayed. However, the metadata popup can be placed within any HTML element.
Key UI Components
This section provides a more detailed look at some of the key components of the Dashboard user interface, including the following:
Note:
This section acts an introduction to these components. Review the Dashboard code for better examples of their use in practice.
Dashboard API Service
The Dashboard API is a service that facilitates quick and easy use of the REST API. It contains Javascript functions that make the necessary HTML requests, and handle the responses from the server. It also stores the settings regarding the data to be retrieved.
As with all AngularJS services, to use the Dashboard API you must include it within a controller to access its functionality.
angular.module('exampleModule', [])
	.controller('exampleController', ['$scope', 'dashboard',function($scope, dashboard) {
		// the dashboard var contains all functionality provided by the dashboardApi service
} ] );
REST API Abstraction
Each of the four routes provided by the REST API has a corresponding function in the Dashboard API service. This makes getting data from the server as simple as calling a JavaScript function.
Note:
This section only covers the dashboard API’s functions. The REST API's schema is defined in the Polymetric Dashboard REST API section of this documents.
The functions are:
All Dashboard API methods return a promise. So to access the response you must define a success callback. You can also set up an error callback to handle any errors, however the Dashboard API handles most of the error handling by itself.
dashboard.getSensor(namespace, sensor, item)
	.then (function(sensorProps) { // success callback
		// ...
	}, function(error) { // failure callback
		// ...
	});
Data Settings
The Dashboard API also stores the settings regarding how data should be formatted and the time ranges it should span. These settings are specified using the Settings Dialog and are:
UpdateProvider Service
The UpdateProvider is a service that calculates the time to wait before new data is available on the server. It is used by the visualization tools as it allows them to accurately time their update calls to get new data from the server.
As with all AngularJS services, to use the UpdateProvider you must include it within a controller. However, you must instantiate an "updater" to access the functionality.
angular.module('exampleModule', [])
	.controller('exampleController', ['$scope', '$element', 'UpdateProvider', function($scope, $element, UpdateProvider) {
		var updater = UpdateProvider.updater($element, $scope, lastUpdate, callback);
		// the updater variable has all the funcitonality needed to correctly time the update REST calls
});
The updater requires four arguments:
Additionally, the update needs to know the readingInterval of the sensor the visualization tool is displaying.
dashboard.getSensor(namespace, sensor, item)
	.then(function(sensorProps) { // if the sensor was found this function is called
		updater.readingInterval = sensorProps.readingInterval;
	});
Finally, after handling a REST API data response call the updater's delay function passes in the newest datapoints timestamp. The updater calculates the appropriate delay to wait before it makes the next call to the callback function it was initialized with.
dashboard.getChartData(namespace, sensor, item, startTime, samplePeriod)
	.then(function(data) { // if data was retrieved this function is called
		// do something with the data...
		// the newest data point is the last one of the returned data array
		updater.delay(data[data.length - 1].timestamp);
	});
Note:
The UpdateProvider contains functionality that reduces the amount of unnecessary REST API calls, greatly increasing the performance of the Dashboard.
The Polymetric Dashboard REST API
The REST API provides access to the data stored on the monitoring infrastructure. This section contains the following REST API topics:
Route Overview of the REST API
Every REST API call is prefaced with the base path,  /api/dashboard/.
After the base path comes the version of the REST API, this allows for new versions to be created without breaking or removing current functionality. The current version is v3/.
For example, to get all of the sensors in the monitoring infrastructure make an HTTP GET request with the URL:
http:\\localhost:<port>\api\dashboard\v3\Sensors?encryption=none
Note:
All data received from the REST API is formatted as a JSON string.
All timestamps sent and received from the REST API should be in non-localized UTC format:
Description of REST API Routes
The following four routes are provided by the REST API:
Get All Sensors
The response contains an array of objects representing all of the sensors registered by the Dashboard. Each sensor's identifiers and properties are included within the objects.
[
{
	"namespace": string,
	"sensor" string,
	"item": string,
	"criticalValue": string,
	"warningValue": string,
	"units": string,
	"operator": string,
	"description": string,
},
]
Get a Sensor
The response contains one object representing a single sensor registered by the Dashboard. Only the sensor's properties are included within the object as the identifiers must be known already to make this call.
{
	"criticalValue": string,
	"warningValue": string,
	"units": string,
	"operator": string,
	"description": string,
}
Get a Sensor’s Chart Data
The response contains an array of objects representing a single sensor's readings, starting from the current time and ending at a specified time in the past. Each object contains the time and value of the reading.
[
{
	"value": number,
	"timestamp": string,
},
] 
Get a Sensor’s Calculated Data
The response contains one object representing a single sensor's calculated data, starting from the current time and ending at a specified time in the past. The object contains the sensor's state, max value, min value, mean value, and standard deviation.
{
	"state": integer,
	"min": number,
	"max": number,
	"mean": number,
	"stdDev": number,
	"timestamp": string
}
The Polymetric Dashboard Monitoring Infrastructure
The monitoring infrastructure provides users with the functionality to create their own sensors. When user-defined sensors are added to the Dashboard, the data they collect is automatically accessible from the Dashboard user interface. This section covers the following monitoring infrastructure topics:
Sensor Namespaces
The Dashboard's default collection of sensors is defined in the %SYS namespace because it is maintained by InterSystems. Users are strongly discouraged from changing the default sensor definitions or creating new sensor definitions in the %SYS namespace. The Dashboard can collect data from sensors in any namespace, so user-defined sensors should be located in a user namespace.
Note:
To customize the default Dashboard sensors, make a copy of the %SYS.Monitor.DashboardSensors class and place it in a USER namespace, for example, USER.Monitor.DashboardSensors, and then alter the sensor functionality. The default sensors can then be removed and the altered sensors within the USER namespace can be added. The process of adding and removing sensors in described in detail below.
Creating User-Defined Sensors
The monitoring infrastructure allows users to create their own custom sensors to fit the needs of their organization. This section includes a the following topics:
Important:
To provide a concrete example of the steps required to create sensors, this section describes how to create three sample custom sensors in the %SYS namespace. However, users should not create sensors in the %SYS namespace.
Sensor Creation Procedure Overview
The basic procedure for creating a sensor is as follows:
  1. Create a sensor collection class. This class must inherit from the %SYS.Monitor.AbstractDashboard class.
  2. Create the Start Method that calls CreateSensor() for each individual sensor you want to define. The Method Start() As %Status {...} overrides %SYS.Monitor.AbstractDashboard.Start() and is called automatically during the initialization of the Dashboard.
  3. Create the GetSensors Method calling SetSensor() for each individual sensor you want to record data from. The Method GetSensors() As %Status {...} overrides %SYS.Monitor.AbstractDashboard.GetSensors() and is called automatically during each sampling period.
  4. Use the System Monitor Manager (^%SYSMONMGR) to add your new sensor collection class to System Monitor. The System Monitor Manager can also be used to remove sensor collection classes.
Sample Sensors Overview
The three new sensors that monitor the CACHEAUDIT database are:
These sensors represent the three types of sensors that are used within the Dashboard, as follows:
Create a Sensor Collection Class
Using Atelier, create a sensor collection class as follows.
  1. Open Atelier.
  2. If you do not have a current project, create a new one.
  3. Select File > New > Class File. This displays the New Class File wizard.
  4. Under General, select Empty Class.
  5. Click Next.
  6. On the New Empty Class File page, complete the fields as follows:
  7. Select More.
  8. In the search field, enter %SYS.Monitor.AbstractDashboard and select it in the results.
  9. Click OK.
  10. In the description box, add a description.
  11. Click Finish.
Now you have created the sensor collection, you must create two methods: Start() and GetSensors(). Create these methods now and fill in their functionality later.
  1. Create the Start() Method; this is where the sensors will be created.
    /// Initialize all the sensors
    Method Start() As %Status   {
    	Set ..State="OK"
    	Quit $$$OK
    }
  2. Create the GetSensors() Method, this is where the sensor values will be read.
    Important:
    All the functionality must be placed inside a try-catch statement. If errors occur during the GetSensors() method call they should not be thrown because this would halt the entire System Monitor process. Instead set the sensor collection class's state using the ..State property.
/// Read data from all the sensors
Method GetSensors() As %Status {
	Set ..State="OK"
	Try {
		// Functionality to get metrics recorded by sensors will go here
	Catch {
		// Set the sensor collection class's state to "ERROR", but do not throw any errors
		Set ..State="ERROR"

	}
	// Always Quit $$$OK as to not halt the Polymetric Dashboard
	Quit $$$OK

}
Create the TotalAudits Sensor
The TotalAudits sensor is the simplest form of sensor.
  1. Add a call to CreateSensor() in the Start() method to create the TotalAudits sensor.
    Do ..CreateSensor("TotalAudits","","","",0,"",">","The total number of audits in the CACHEAUDIT database")
  2. In the GetSensors() method call SetSensor() to populate readings of the TotalAudits Sensor.
// The global ^["^^cacheaudit"]CACHEAUDITD stores the total number of audits
Set NumberOfAudits = ^["^^cacheaudit"]CACHEAUDITD
// Record the value
Do ..SetSensor("TotalAudits", NumberOfAudits)
Create the AuditsPerSecond Sensor
The AuditsPerSecond sensor builds off the TotalAudits sensor, adding functionality to compute the change in the value of the metric.
  1. Add two new properties to the sensor collection class, one called PrevReadingTime and the other ElapsedSeconds. These are used to calculate the total time since the last reading of the sensors.
    // Time of previous sampling period (Initialize to -1 to indicate the initial sampling period)
    Property PrevReadingTime As %Integer [ InitialExpression = -1 ];
    /// Elapsed seconds since the last sampling period
    Property ElapsedSeconds As %Integer;
  2. Add a new property to the sensor collection class called PrevNumberOfAudits; this stores the last value so it can be compared to the current value.
    /// Value of NumberOfAudits during the last GetSensors() call
    Property PrevNumberOfAudits As %Integer [InitialExpression = 0];
  3. In the Start() method, add a call to CreateSensor() to create the AuditsPerSecond sensor.
    Do ..CreateSensor("AuditsPerSecond","","","",0,"",">","The number of audits added to the CACHEAUDIT database per second")
  4. In the GetSensors() Method and after the TotalAudits functionality created before, get the current time.
    //$PIECE($ZHOROLOG, ".", 1) gets the current internal time.
    Set CurReadingTime = $PIECE($ZHOROLOG, ".", 1)
  5. Add an if statement to test that the current sampling period is not the first one. This is because the difference in a metric's value cannot be calculated without a previous reading.
    // A previous reading must have been taken for change to be calculated
    If (..PrevReadingTime '= -1) {
        // Delta calculations will be completed here
    }
  6. In the new if statement, calculate the total time since the last sampling period.
    // Calculate the difference between the time of the last sampling period and the current time
    Set ..ElapsedSeconds = (CurReadingTime - ..PrevReadingTime)
  7. After calculating ..ElapsedSeconds and within the if statement, calculate the difference between the current number of audits.
    // Calculate the difference between the total number of audits during the current and last sampling period
    Set DeltaAudits = NumberOfAudits - ..PrevNumberOfAudits
  8. Calculate the change in number of audits per second.
    Set NumberOfAuditsPerSecond = DeltaAudits / ..ElapsedSeconds
  9. Set the AuditsPerSecond sensor to the calculated reading.
    // Record the value
    Do ..SetSensor("AuditsPerSecond", NumberOfAuditsPerSecond)
  10. Outside and after the if statement, store this reading’s total audits and the current time to be used during the next sampling period.
    // Store the current number of audits
    Set ..PrevNumberOfAudits = NumberOfAudits
    // Store the current time
    Set ..PrevReadingTime = CurReadingTime
Create the TotalAuditsOfType Sensor
The TotalAuditsOfType sensor is similar to the TotalAudits sensor as it records total values, not deltas; however, it utilizes items that record the total number of audits grouped by type. It is also possible to store deltas in this type of sensor.
  1. Add a call to CreateSensor() in the Start() method to create the AuditsPerSecond Sensor.
    Do ..CreateSensor("TotalAuditsOfType","","","",0,"",">","The total number of audits in the CACHEAUDIT database grouped by type")
  2. Add a new property to the sensor collection class called TotalAuditsOfType; this is used as temporary storage for audit counts by type.
    /// Temp storage for Total Audits of Type
    Property TotalAuditsOfType [ MultiDimensional ];
  3. In the GetSensors() Method, add a loop that parses through all the audits in the CACHEAUDIT database and the number of each distinct type of audit.
    // Get the first pointer in the Audit Global
    Set node = $QUERY(^["^^cacheaudit"]CACHEAUDITD)
    // Loop through all of the audits
    While (node '= "") {
         // Extract the 5th element of the entry (type of audit)
         Set auditType = $LIST(@node,5)
         // Add to counts, use $GET to start at 0 for novel types
         Set ..TotalAuditsOfType(auditType) = $GET(..TotalAuditsOfType(auditType), 0) + 1
         // Get the next pointer
         Set node = $QUERY(@node)
    }
  4. After the counting loop add another loop that calls SetSensor() for each audit type, recording the count of each type of audit.
    // Iteration begins at the empty string
    Set item = ""
    For {
         // Get the next item held in the multidimensional property
         Set item = $ORDER(..TotalAuditsOfType(item)) Quit:item=""
         // Set the sensor's item which is dynamically created if it does not exist
         Do ..SetSensor("TotalAuditsOfType",..TotalAuditsOfType(item), item)
         // Reset the counts to 0 after recording it
         Set ..TotalAuditsOfType(item) = 0
    }
Note:
You must define an item for each type of audit with the use of the third argument of SetSensor() (something that was not done in either of the other two sensors). Passing in this third parameter enables the Dashboard to create a new sensor item if it does not exist, then increment that item's count.
Adding and Removing Sensor Classes and Namespaces
Sensors that have been created must be added to System Monitor. Sensors can also be removed from System Monitor.
Adding Custom Sensors
To add your custom sensors to System Monitor, do the following:
  1. Open a Caché Terminal window and switch to the namespace that the sensors are stored in:
    zn "<namespace>"
  2. Run the System Monitor Manager routine:
    Do ^%SYSMONMGR
  3. Enter 2 for Add Class.
  4. Enter the name of your custom sensor class, such as the %SYS.Monitor.AuditSensors class.
  5. Enter a description.
  6. Exit to the main menu by entering 4 then 3.
Adding the Namespace
The System Monitor runs only in the %SYS namespace by default, but you can add other namespaces as needed. You must add every namespace in which you create and add sensors to enable the Dashboard to record data from those sensors.
Note:
A namespace need only be registered once. If a namespace is already registered, new sensor collection classes within that namespace can be added without re-regestering the namespace.
To add the namespace containing the custom sensors you just added, do the following:
  1. Open a Caché Terminal window and switch to the %SYS namespace:
    zn "%SYS"
  2. Run the System Monitor Manager routine:
    Do ^%SYSMONMGR
  3. Enter 2 for Add Namespace.
  4. Enter the namespace containing your custom sensor class, for example USER.
  5. Restart Caché.
With the sensor class and namespace added, the new sensors are added to the Dashboard user interface. Wait a minute for data to be collected and then refresh the Dashboard in the browser, the new sensors should now be displayed on the All Sensors tab.
Removing Sensors
You can also remove a custom sensor class from System Monitor, as follows:
  1. Open a Caché Terminal window and switch to the %SYS namespace:
    zn "<namespace>"
  2. Run the System Monitor Manager routine:
    Do ^%SYSMONMGR
  3. Enter 3 for Delete Class.
  4. Enter the name of your custom sensor class, such as %SYS.Monitor.AuditSensors, or enter ? (question mark) and choose the sensor class from a list.
  5. Exit to the main menu by entering 4 then 3.
Removing a Namespace
You can remove a namespace (other than%SYS) from System Monitor, as follows:
  1. Open a Caché Terminal window and switch to the %SYS namespace:
    zn "%SYS"
  2. Run the System Monitor Manager routine:
    Do ^%SYSMONMGR
  3. Enter 3 for Delete Namespace.
  4. Enter the namespace you want to remove, for example, USER.
    Note:
    If you need to remind yourself of the namespaces that have been added, before choosing 3, choose 1 for List Namespaces.
  5. Restart Caché.