Name

global.DiscoveryTimeline

Description

Schedule page script include for the generating a view that shows discovery probes and sensors on the Timeline. See the TimelineAbstractSchedulePage script include description for correct implementation and API usage.

Script

// Discovery

/**
* This script include parses an ecc_queue and performs custom sorting against the items returned to display
* a list of node of sensor times with respect to their probe. 
*
* @author Mark.Johnson && Modified by Aleck.Lin
*/

// Class Imports
var TimelineItem = GlideTimelineItem;

// Constants
TABLE_NAME = 'ecc_queue';
DEVICE_SOURCE = 'sysparm_timeline_source';
AGENT_CORRELATOR = 'sysparm_timeline_agent_correlator';

var DiscoveryTimeline = Class.create();
DiscoveryTimeline.prototype = Object.extendsObject(AbstractTimelineSchedulePage, {

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // GET_ITEMS
  //////////////////////////////////////////////////////////////////////////////////////////////////

  getItems: function() {
  	var device_source = this.getParameter(DEVICE_SOURCE);
  	var agent_correlator = this.getParameter(AGENT_CORRELATOR);
  	this._mapProbesAndSensors();
  	this._constructPageTitle(agent_correlator, device_source);

  	// Query database and parse each item
  	var gr = new GlideRecord(TABLE_NAME);
  	gr.orderBy('sys_created_on');

  	addQuery(gr);

  	var extra = gs.getMessage("The limit is set by the property glide.discovery.timeline.max_entries.");
  	var max = gs.getProperty('glide.discovery.timeline.max_entries');

  	if (max > 25000) {
  		max = 25000;
  		extra = gs.getMessage("This limit cannot be changed due to PRB1402520.");
  	}
  	gr.setLimit(max);

  	gr.query();

  	if (gr.getRowCount() >= max) {
  		var count = countECCRecords();
  		if (count > max) {
  			gs.addErrorMessage(gs.getMessage("The number of ECC queue entries ({0}) in this discovery exceeds the allowable limit ({1}). {2}", [ count, max, extra ]));
  			gs.addInfoMessage(gs.getMessage("Showing only {0} items", max));
  		}
  	}

  	while (gr.next())
  		this._parseItem(gr);

  	// Now render the list of items
  	this._treeify();
  	for (var i = 0; i < this._item_roots.length; i++)
  		this._renderTree(this._item_roots[i]);

  	function addQuery(gr) {
  		// TODO: is this the right thing to do?  Shouldn't we abort the whole thing, not get all records?
  		if (JSUtil.notNil(agent_correlator))
  			gr.addQuery('agent_correlator', agent_correlator);

  		if (JSUtil.notNil(device_source)) {
  			var q = gr.addQuery('source', device_source);
  			q.addOrCondition("topic", "Shazzam");
  		}
  	}

  	function countECCRecords() {
  		var ga = new GlideAggregate("ecc_queue");
  		addQuery(ga);
  		ga.addAggregate('COUNT');
  		ga.query();
  		if (ga.next())
  			return ga.getAggregate('COUNT');
  	}
  },


  /**
 * Recursive depth-first item tree traversal...
 */  
  _renderTree: function(item) {
  	this._renderItem(item);
  	var kids = item.kids;
  	// sort kids into descending start time order to make the display prettier...
  	kids.sort(function(a, b) {
  		return b.startTime - a.startTime;
  	});
  	for (var i = 0; i < kids.length; i++)
  		this._renderTree(kids[i]);
  },

  _constructPageTitle: function(agent_correlator, device_source) {
  	var pageTitle = "Discovery ";
  	if (JSUtil.notNil(agent_correlator)) {
  		var grs = new GlideRecord("discovery_status");
  		// GlideRecord.get 1 arg form falls back to 'name' if not found by sys_id, 2 arg form throws exception if arg2 is null.
  		if (grs.get('sys_id', agent_correlator))
  			pageTitle += grs.number.toString();
  		else
  			pageTitle += 'UNKNOWN';
  	}
  	pageTitle += ' Timeline';   
  	var titleSuffix = "";

  	// if we're showing a device timeline...
  	if (JSUtil.notNil(device_source)) {
  		titleSuffix = " for " + device_source;
  		var gr = new GlideRecord('discovery_device_history');
  		gr.addQuery('source', device_source);
  		gr.addQuery('status', agent_correlator);
  		gr.query();
  		if (gr.next()) {
  			var cigr = new GlideRecord('cmdb_ci');
  			cigr.addQuery('sys_id', gr.cmdb_ci);
  			cigr.query();
  			if (cigr.next()) 
  				titleSuffix = " for " + cigr.name + " (" + device_source + ")";
  		}
  	}

  	this.setPageTitle(pageTitle + titleSuffix);
  },

  _renderItem: function(queueItem) {
  	var item = new TimelineItem(TABLE_NAME, queueItem.sysId);
  	var span = item.createTimelineSpan(TABLE_NAME, queueItem.sysId);
  	span.setTimeSpan(queueItem.startTime, queueItem.endTime);
  	span.setSpanText(queueItem.text);
  	span.setSpanColor(queueItem.spanColor);
  	span.setInnerSegmentTimeSpan(queueItem.innerSegStartTime, queueItem.innerSegEndTime);
  	span.setInnerSegmentClass(queueItem.innerClass);

  	if (queueItem.predecessor)
  		span.addPredecessor(queueItem.predecessor);
  	var tooltip = '';
  	for (var i = 0; i < queueItem.tips.length; i++) {
  		var tip = queueItem.tips[i];
  		tooltip += '<strong>' + tip.title + ': </strong>' + tip.value + '<br/>';
  	}
  	if (queueItem.cidata)
  		tooltip += queueItem.cidata;
  	if (tooltip)
  		span.setTooltip(tooltip);

  	// Finally, add the item (which contains the span).
  	this.add(item);
  },

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // CORE OBJECT
  //////////////////////////////////////////////////////////////////////////////////////////////////

  // map of items by sys_id
  _item_map: {},
  // root item (Shazzam probe)...
  _item_roots: [],

  _parseItem: function(gr) {
  	var payload = '' + gr.payload;
  	var name = '' + gr.name;
  	var isProbe = (gr.queue == 'output');

  	var probe = this._getProbe(payload, name);
  	var item = this._getItem(gr, probe, isProbe, payload, name);
  	this._addTooltips(item, gr, probe, isProbe, payload, name);

  	this._item_map[item.sysId] = item;
  },

  _addTooltips: function(item, gr, probe, isProbe, payload, name) {
  	if (isProbe)
  		this._addTooltip(item, "Probe", probe.probe_name);
  	else
  		this._addTooltip(item, "Sensor", probe.sensor_name);

  	this._addTooltip(item, "Topic", gr.topic.toString());
  	this._addTooltip(item, "Name", gr.name.toString());
  	var agent = '' + gr.agent;
  	agent = agent.replace('mid.server.', '');
  	this._addTooltip(item, "Agent", agent);

  	if (!isProbe) {
  		var i = payload.indexOf(' probe_time="');
  		if (i >= 0) {
  			var start = i + 13;
  			var end = payload.indexOf('"', start);
  			var probeMS = payload.substring(start, end);
  			var pred = this._item_map[item.predecessor];
  			this._addTooltip(pred, "Probe execution time", (probeMS/1000).toFixed(2) + ' Seconds'); // Convert it to the neareast 2nd decimal
  		}
  	}

  	this._getProcessedTime(item, gr);
  	this._parsePayload(item, payload, name);
  },

  _getItem: function(gr, probe, isProbe, payload, name) {
  	var item = {};
  	item.sysId = '' + gr.sys_id.toString();
  	item.startTime = gr.sys_created_on.getGlideObject().getNumericValue();
  	item.endTime = gr.sys_updated_on.getGlideObject().getNumericValue();
  	item.innerSegStartTime = gr.sys_created_on.getGlideObject().getNumericValue();
  	item.innerSegEndTime = gr.processed.getGlideObject().getNumericValue();
  	item.innerClass = isProbe ? 'silver' : 'green';
  	item.isProbe = isProbe;
  	item.spanColor = isProbe ? '#000000': 'tomato';
  	item.text = '' + gr.source;
  	if (item.text.indexOf('.') >= 0)
  		item.text += ': ';
  	else
  		item.text = '';
  	item.text += isProbe ? probe.probe_name : probe.sensor_name;
  	item.kids = [];
  	item.tips = [];

  	if (JSUtil.notNil(gr.response_to))
  		item.predecessor = '' + gr.response_to.toString();

  	return item;
  },

  _getProbe: function(payload, name) {
  	var probe = this.probeMap[name];
  	var i = payload.indexOf('<parameter name="probe" value="');
  	if (i >= 0) {
  		var probe_sys_id = payload.substr(i + 31, 32);
  		probe = this.probeMap[probe_sys_id];
  	}
  	return probe;
  },

  _treeify: function() {
  	for (var sys_id in this._item_map) {
  		var item = this._item_map[sys_id];
  		if (item.predecessor) {
  			var parent = this._item_map[item.predecessor];
  			if (parent)
  				parent.kids.push(item);
  			else
  				this._item_roots.push(item);
  		} else
  			this._item_roots.push(item);
  	}
  	// sort roots into ascending start time order to make the display prettier...
  	this._item_roots.sort(function(a, b) {
  		return a.startTime - b.startTime;
  	});
  },

  _getProcessedTime: function(item, gr) {
  	var timeStart     = parseInt( gr.sys_created_on.getGlideObject().getNumericValue(), 10);
  	var timeProcessed = parseInt( gr.processed.getGlideObject().getNumericValue(), 10);
  	var timeEnd       = parseInt( gr.sys_updated_on.getGlideObject().getNumericValue(), 10);

  	if (gr.queue == 'output') {
  		this._addTooltip(item, "Instance queue time", this._getTimeDisplay(timeProcessed - timeStart));
  		this._addTooltip(item, "MID server processing time", this._getTimeDisplay(timeEnd - timeProcessed)); 
  	} else if (gr.queue == 'input') {
  		this._addTooltip(item, "Instance queue time", this._getTimeDisplay(timeProcessed - timeStart));
  		this._addTooltip(item, "Instance processing time", this._getTimeDisplay(timeEnd - timeProcessed)); 
  	}
  },

  _getTimeDisplay: function(timeDuration) {
  	if (timeDuration < 0)
  		timeDuration = 0;

  	var d = new GlideDuration(); 
  	d.setNumericValue(timeDuration);
  	return d.getDisplayValue();
  },

  _parsePayload: function(item, payload, name) {
  	if (JSUtil.nil(payload))
  		return;

  	if ((name.indexOf("Classify") < 0) && (name.indexOf("Identity") < 0))
  		return;

  	var i = payload.indexOf('<parameter name="cidata" value="');
  	if (i < 0)
  		return;

  	var start = i + 32;
  	var end = payload.indexOf('"', start);
  	var ciDataXML = payload.substring(start, end);
  	ciDataXML = JSUtil.unescapeText(ciDataXML);
  	ciDataXML = ciDataXML.replace(JSUtil.QT_ENT, '"');
  	var ciData = new CIData();
  	ciData.fromXML(ciDataXML);
  	item.cidata = '<div style="margin:6px 12px; background-color:#ccc; font:0; height:1px;"></div><strong>CI Data: </strong>' + 
  		this._constructCIDataOutput(ciData);
  },

  /**
 * Makes a single query of the probe and sensor tables to build a map of information that's used later to show probe and sensor names.
 */
  _mapProbesAndSensors: function() {
  	var smap = {};
  	var sgr = new GlideRecord('discovery_sensor');
  	sgr.query();
  	while (sgr.next()) {
  		var probe = '' + sgr.reacts_to_probe;
  		var name = '' + sgr.name;
  		smap[probe] = name;
  	}

  	var pmap = {};
  	var pgr = new GlideRecord('discovery_probes');
  	pgr.query();
  	while (pgr.next()) {
  		var probe = {};
  		probe.sys_id = '' + pgr.sys_id;
  		probe.probe_name = '' + pgr.name;
  		probe.sensor_name = smap[probe.sys_id];
  		probe.ecc_queue_name = '' + pgr.ecc_queue_name;
  		pmap[probe.sys_id] = probe;
  		pmap[probe.ecc_queue_name] = probe;
  	}
  	this.probeMap = pmap;
  },

  _constructCIDataOutput: function(ciData) {
  	var output = ciData.toString();
  	output = output.substr(17);
  	return '<div style="white-space:pre;">' + output + "</div>";
  },

  _addTooltip: function(item, title, value) {
  	var tip = {};
  	tip.title = title;
  	tip.value = value;
  	item.tips.push(tip);
  },

  type: 'DiscoveryTimeline'
});

Sys ID

1f7daf220a0a0b943bd12d05134ffd7b

Offical Documentation

Official Docs: