Name
sn_app_insights.MetricDataRetriever
Description
No description available
Script
var MetricDataRetriever = Class.create();
MetricDataRetriever.prototype = {
initialize: function() {
this.X_AXIS = "x";
this.X_AXIS_KEY = "timeStamp";
this.Y_AXIS = "y";
this.TYPE = "type";
this.LINE_TYPE = "line";
this.SCATTER_TYPE = "scatter";
this.LABEL = "label";
this.LINE_CONFIG_KEY = "line";
this.AVG_CONFIG_KEY = "1-day-avg";
this.SYS_CLUSTER_STATE = "sys_cluster_state";
this.ECC_QUEUE_STATS_BY_ECC_AGENT = "ecc_queue_stats_by_ecc_agent";
this.EXCLUSION_GROUPING_LIST = [this.AVG_CONFIG_KEY];
},
/**
* @param secondsInTimeWindow: The number of seconds in the past for the time window. For example, for 604800
* the payload would contain 7 days worth of data.
*
* @param tableName: The table name the metric subject(s) resides in. For example, sys_pattern for slow patterns.
* Required because MetricBase enforces uniqueness based on table name and metric name, not just metric name.
*
* @param metricName: The metric name for the requested metric values. For example, execution_count for slow patterns.
*
* @returns A JSON payload containing metric values over a 7 day period for n lines and a config containing
* metadata for each line. The payload is formatted to be consumed by the seismic timeseries chart component. For
* example, this means that each point in time's timeStamp is in EPOC seconds and not milliseconds.
* An example payload:
{
"seriesConfig": {
"line0": {
"label": "192.168.1.8:paris",
"type": "line",
"x": "timeStamp",
"y": "y0"
},
"line1": {
"label": "192.168.1.8:paris2",
"type": "line",
"x": "timeStamp",
"y": "y1"
}
},
"seriesData": [
{
"timeStamp": 1603473000,
"y0": 37.70872116088867,
"y1": 36.847618103027344
},
{
"timeStamp": 1603473300,
"y0": 36.0965690612793,
"y1": 35.09318161010742
},
{
"timeStamp": 1603473600,
"y0": 35.38829040527344,
"y1": 34.14033126831055
}
]
}
*/
getMetricValuesPayload: function(secondsInTimeWindow, tableName, metricName, filter, plotType) {
// 7 days in seconds
var end = new GlideDateTime();
var start = new GlideDateTime(end);
start.addSeconds(-1 * secondsInTimeWindow);
// query subject records
var metricSubjects = new GlideRecord(tableName);
if (tableName == this.SYS_CLUSTER_STATE)
metricSubjects.addQuery("status", "online");
if (filter)
metricSubjects.addEncodedQuery(filter);
metricSubjects.query();
if (metricSubjects.getRowCount() <= 0)
return {
'seriesConfig': {},
'seriesData': [],
'thresholdData': {}
};
/**
* There is currently a bug in MetricBase that returns data at a 1 second period
* instead of the period configured in the retention policy. This then makes the
* data array returned by MetricBase filled with a significant number of NaNs. So,
* to workaround this, we resample the data to fit a period we specify. If we're
* getting node metrics, the period for the scheduled job that persists node metrics
* is used. Otherwise, we use a default 5 minute period.
*/
var resamplePeriodDuration;
if (tableName == this.SYS_CLUSTER_STATE) {
var scheduledJob = new GlideRecord("sysauto_script");
scheduledJob.get("8c94b336c37310107f5633f6bb40dddc");
resamplePeriodDuration = new GlideDuration(scheduledJob.run_period.dateNumericValue());
} else {
resamplePeriodDuration = new GlideDuration(5 * 60 * 1000);
}
var transformer = new sn_clotho.Transformer(metricSubjects);
transformer.metric(metricName).resample("AVG", resamplePeriodDuration);
// execute and return result for visualizing
var resultArray = transformer.execute(start, end).toArray();
//Build threshold data before calculating moving average so moving average does not skew threshold
var payload = {};
var thresholdUtils = new ThresholdUtils();
payload["thresholdData"] = thresholdUtils.buildThresholdData(resultArray, metricName, tableName);
payload["thresholdConfig"] = thresholdUtils.buildThresholdConfig();
//Get new TransformPart for moving average and concatenate to result array
transformer = new sn_clotho.Transformer(metricSubjects);
transformer
.metric(metricName)
.resample("AVG", resamplePeriodDuration)
.filter("AVG", new GlideDuration(24 * 60 * 60 * 1000))
.avg(); // Need this last average to turn it into a single line for a moving average
resultArray = resultArray.concat(transformer.execute(start, end).toArray());
//Used to define aggregate labels and at which index they are in resultArray
var aggregateIndices = {};
aggregateIndices[resultArray.length - 1] = gs.getMessage('1-Day Moving Average');
//Build config with all data
payload["seriesConfig"] = this._buildSeriesConfigForGraph(resultArray, aggregateIndices, plotType);
payload["seriesData"] = this._buildSeriesDataWithMetricValues(resultArray);
var annotationsDataUtils = new AnnotationsDataUtils();
payload["annotationsConfig"] = annotationsDataUtils._getAnnotationsConfig();
var annotationsData = annotationsDataUtils._getAnnotationsData(start);
payload["annotationsData"] = annotationsData.data;
payload["annotationsMetadata"] = annotationsData.counts;
return payload;
},
getClusteredMetricValuesPayload: function(secondsInTimeWindow, tableName, metricName, filter, plotType) {
var originalPayload = this.getMetricValuesPayload(secondsInTimeWindow, tableName, metricName, filter, plotType);
if (!this.canCluster(originalPayload))
return originalPayload;
var kmeanClusters = new Kmeans();
var clusteredPayload = kmeanClusters.processAndClusterData(originalPayload, this.EXCLUSION_GROUPING_LIST);
return clusteredPayload;
},
canCluster: function(originalPayload) {
var minNumberOfLinesToGroup = gs.getProperty('glide.appinsights.min_number_of_lines_to_group', 7);
if (Object.keys(originalPayload["seriesConfig"]).length > minNumberOfLinesToGroup)
return true;
return false;
},
/**
* @private
* @param resultArray The result array returned by MetricBase. For example, this array would contain all
* metric values across all nodes
*
* @returns The seriesConfig object required by a seismic time series chart. Example:
*
{
node0: { x: "timeStamp", y:"y0", type: "line", label: "node0SystemId" },
node1: { x: "timeStamp", y:"y1", type: "line", label: "node1SystemId" }
}
*/
_buildSeriesConfigForGraph: function(resultArray, aggregateIndices, plotType) {
var seriesConfig = {};
for (var i = 0; i < resultArray.length; i++) {
var singleLineConfig = {};
singleLineConfig[this.X_AXIS] = this.X_AXIS_KEY;
singleLineConfig[this.Y_AXIS] = this.Y_AXIS + i;
singleLineConfig[this.TYPE] = this.LINE_TYPE;
if (aggregateIndices && aggregateIndices.hasOwnProperty(i)) {
singleLineConfig[this.LABEL] = aggregateIndices[i];
singleLineConfig[this.TYPE] = this.LINE_TYPE;
seriesConfig[this.AVG_CONFIG_KEY] = singleLineConfig;
continue;
} else {
var tableName = resultArray[i].getTableName();
singleLineConfig[this.LABEL] = this._getLineLabelForSeriesConfig(tableName, resultArray[i].getSubject());
singleLineConfig[this.TYPE] = plotType;
}
seriesConfig[this.LINE_CONFIG_KEY + i] = singleLineConfig;
}
return seriesConfig;
},
_getLineLabelForSeriesConfig: function(tableName, subjectSysId) {
var gr = new GlideRecord(tableName);
if (!gr.get(subjectSysId))
return "";
if (tableName == this.ECC_QUEUE_STATS_BY_ECC_AGENT)
return gr.getValue("agent");
return gr.getValue(gr.getDisplayName());
},
/**
* @private
* @param resultArray The array of metric values for n lines returned by MetricBase
*
* @returns An array of JSON objects where each object contains the metric values, for all lines, at a point in time.
* An example of the return array of JSON objects:
[
{
"timeStamp": 1603473000,
"y0": 37.70872116088867,
"y1": 36.847618103027344
},
{
"timeStamp": 1603473300,
"y0": 36.0965690612793,
"y1": 35.09318161010742
},
{
"timeStamp": 1603473600,
"y0": 35.38829040527344,
"y1": 34.14033126831055
}
]
*/
_buildSeriesDataWithMetricValues: function(resultArray) {
if (resultArray.length == 0)
return [];
var seriesData = this._populateSeriesDataSkeletonWithTimeStamps(resultArray[0]);
for (var lineNumber = 0; lineNumber < resultArray.length; lineNumber++) {
var valuesOfSingleLine = resultArray[lineNumber].getValues();
for (var dataPointIndex = 0; dataPointIndex < seriesData.length; dataPointIndex++) {
var pointInTime = seriesData[dataPointIndex];
// The NaN object gets turned to a string when it exists in the return, but null doesn't. So
// make all NaN objects null objects so the front-end can process it correctly.
var metricValue = valuesOfSingleLine[dataPointIndex];
pointInTime[this.Y_AXIS + lineNumber] = isNaN(metricValue) ? null : valuesOfSingleLine[dataPointIndex];
}
}
return seriesData;
},
/**
* @private
* @param singleResultArray An array of metric values for a single line.
*
* @returns An array of JSON objects that only contains the time stamps based on the time period of the result and
* the number of results in the 0th line.
*
* NOTE: If the retrieved metric data contains more than one line, this method assumes that all lines have the same
* number of values as the 0th line. This assumption was made because we have MetricBase resample the data to a
* set time period and therefore there should only be a single value per point in time.
*
* An example of the returned array of JSON objects
[
{
"timeStamp": 1603473000
},
{
"timeStamp": 1603473300
},
{
"timeStamp": 1603473600
}
]
*/
_populateSeriesDataSkeletonWithTimeStamps: function(singleResultArray) {
// Create new gdt so we don't modify the original in the result
var indexDateTime = new GlideDateTime(singleResultArray.getStart());
var seriesDataSkeleton = [];
var valueArray = singleResultArray.getValues();
for (var i = 0; i < valueArray.length; i++) {
var localTimestamp = new GlideDateTime();
localTimestamp.setValue(indexDateTime.getDisplayValueInternal());
seriesDataSkeleton.push({
"timeStamp": localTimestamp.getNumericValue()
});
// Each value in the valueArray is separated from the previous value by an amount
// of time (ex: 300 seconds), which is the time period. So to get the time stamp for the
// next value, we need to increment by the period to get the time stamp related to the value
indexDateTime.addSeconds(singleResultArray.getPeriod());
}
return seriesDataSkeleton;
},
type: 'MetricDataRetriever'
};
Sys ID
a48ae8e1c30820107f5633f6bb40dd56