Name

global.ECCQueueStatsByECCAgent

Description

Computes ECC Queue Stats per ECC Agent for consumption by XML Stats and monitoring.

Script

var ECCQueueStatsByECCAgent = Class.create();

ECCQueueStatsByECCAgent.prototype = {
  
  /***********************************************************************************************************/
  initialize: function() {
  	this.TABLE_NAME__ECC_QUEUE_STATS_BY_ECC_AGENT = 'ecc_queue_stats_by_ecc_agent';
  	this.AGENT_NAME_PREFIX = 'mid.server.';
  	this.BROADCAST_AGENT_NAME = this.AGENT_NAME_PREFIX + '*';
  	this.Metrics = Object.freeze({
  		OLDEST_ENTRY_DATE: {aggregate: 'MIN', field: 'sys_updated_on', value: 'oldest_entry_date'},
  		CURRENT_COUNT: {aggregate: 'COUNT', field: 'queue', value: 'current_count'},
  		INTERVAL_COUNT: {aggregate: 'COUNT', field: 'queue', value: 'interval_count'},
  		AVG_CREATED_ON: {aggregate: 'SUM', field: 'sys_created_on', value: 'avg_created_on'},
  		AVG_PROCESSED: {aggregate: 'SUM', field: 'sys_updated_on', value: 'avg_processed'}
  	});
  	this.new_last_refreshed_date = new GlideDateTime();
  	this.agents = {};                           // map of ECC Agent stats
  	this.interval_duration_seconds = this._computeIntervalDurationSeconds();

  	this.avg_age_ready_input = 0;
  	this.avg_age_ready_output = 0;
  	this.avg_age_processed_output = 0;
  	this.avg_age_processed_input = 0;

  },
  /***********************************************************************************************************/
  refreshECCQueueStatsByECCAgent: function() {
  	// Oldest entry date and current count metrics use the same query, so they are combined here
  	var ga = this._query(this._computeQueryWindowStart(this.Metrics.OLDEST_ENTRY_DATE), this.Metrics.OLDEST_ENTRY_DATE);
  	this._getValuesFromAggregate(ga, [this.Metrics.OLDEST_ENTRY_DATE, this.Metrics.CURRENT_COUNT]);

  	ga = this._query(this._computeQueryWindowStart(this.Metrics.INTERVAL_COUNT), this.Metrics.INTERVAL_COUNT);
  	this._getValuesFromAggregate(ga, [this.Metrics.INTERVAL_COUNT]);

  	ga = this._queryAvg(this._computeQueryWindowStart(this.Metrics.AVG_CREATED_ON), [this.Metrics.AVG_CREATED_ON, this.Metrics.AVG_PROCESSED, this.Metrics.INTERVAL_COUNT]);
  	this._getAvgValuesFromAggregate(ga, this.Metrics.AVG_CREATED_ON, this.Metrics.AVG_PROCESSED);

  	this._updateOrInsertRecords();
  },
  /***********************************************************************************************************/
  /**
   * Computes the interval_duration_seconds based on the last time this date was refreshed.
   */
  _computeIntervalDurationSeconds: function() {
  	var use_fixed_length_interval = JSUtil.toBoolean(gs.getProperty('glide.ecc_queue.stats.collect.use_fixed_length_interval','true'));

  	if (use_fixed_length_interval)
  		return parseInt(gs.getProperty('glide.ecc_queue.stats.collect.interval_duration_seconds','300'),10); // radix is 10

  	var ga = new GlideAggregate(this.TABLE_NAME__ECC_QUEUE_STATS_BY_ECC_AGENT);

  	// The most recent last_refreshed_date in our table
  	ga.addAggregate('MAX', 'last_refreshed_date');
  	ga.query();

  	if (ga.next()) {
  		var old_date = ga.getAggregate('MAX', 'last_refreshed_date');
  		var old_date_gdt = new GlideDateTime(old_date);
  		var old_date_millis = old_date_gdt.getNumericValue();
  		var new_date = new GlideDateTime(this.new_last_refreshed_date);
  		var new_date_millis = new_date.getNumericValue();
  		var interval_duration_millis = new_date_millis - old_date_millis;
  		this.interval_duration_seconds = interval_duration_millis / 1000;
  	}
  },
  /***********************************************************************************************************/
  _computeQueryWindowStart: function(metric) {
  	var duration = this._getQueryWindowDuration(metric);
  	var start = new GlideDateTime(this.new_last_refreshed_date);
  	start.subtract(duration.getNumericValue());

  	// add some time to avoid joining more than 2 shards
  	if (metric !== this.Metrics.INTERVAL_COUNT && metric !== this.Metrics.AVG_CREATED_ON && metric !== this.Metrics.AVG_PROCESSED)
  		start.addSeconds(this.interval_duration_seconds * 2);

  	return start;
  },
  /***********************************************************************************************************/
  /**
   * returns a GlideDateTime which is the duration of the query window
   *
   */
  _getQueryWindowDuration: function(metric) {
  	switch(metric) {
  		case this.Metrics.INTERVAL_COUNT:
  		case this.Metrics.AVG_CREATED_ON:
  		case this.Metrics.AVG_PROCESSED:
  			return new GlideDateTime('1970-01-01 00:05:00');
  		case this.Metrics.CURRENT_COUNT:
  		case this.Metrics.OLDEST_ENTRY_DATE:
  		default:
  			return new GlideDateTime('1970-01-01 04:00:00');  // 12 hours
  	}
  },
  /**********************************************************************************************************/
  _query: function(query_window_start, metric) {
  	var ga = new GlideAggregate('ecc_queue');

  	ga.addQuery('sys_created_on', '>', query_window_start);
  	ga.addQuery('sys_updated_on', '<=', new GlideDateTime());

  	ga.addQuery('agent', 'STARTSWITH', this.AGENT_NAME_PREFIX);
  	ga.addQuery('agent', '!=', this.BROADCAST_AGENT_NAME);

  	ga.groupBy('agent');
  	ga.groupBy('state');
  	ga.groupBy('queue');

  	ga.addAggregate('COUNT', 'queue');

  	if (metric === this.Metrics.OLDEST_ENTRY_DATE || metric === this.Metrics.CURRENT_COUNT) {
  		ga.addAggregate('MIN', 'sys_updated_on');
  	}

  	ga.query();
  	return ga;
  },
  _queryAvg: function(query_window_start, metricsArr) {
  	var ga = new GlideAggregate('ecc_queue');

  	//gs.info('_queryAvg::start time in avg: '+ query_window_start);
  	//gs.info('_queryAvg::end time in avg: '+ this.new_last_refreshed_date);
  	ga.addQuery('sys_created_on', '>', query_window_start);
  	ga.addQuery('sys_updated_on', '<=', this.new_last_refreshed_date);

  	ga.addQuery('agent', 'STARTSWITH', this.AGENT_NAME_PREFIX);
  	ga.addQuery('agent', '!=', this.BROADCAST_AGENT_NAME);
  	ga.addQuery('state', '!=', 'processing');

  	ga.groupBy('agent');
  	ga.groupBy('state');
  	ga.groupBy('queue');

  	for (var i = 0; i < metricsArr.length; i++) {
  		var metric = metricsArr[i];
  		ga.addAggregate(metric.aggregate, metric.field);
  	}

  	ga.query();
  	return ga;
  },
  /**********************************************************************************************************/
  _getValuesFromAggregate: function(ga, metricsArr) {
  	while (ga.next()) {
  		var agent = ga.getValue('agent');
  		var state = ga.getValue('state');
  		var queue = ga.getValue('queue');

  		if (JSUtil.nil(agent) || JSUtil.nil(state) || JSUtil.nil(queue)) {
  			gs.warn('ECCQueueStatsByECCAgent._getValuesFromAggregate() found nil values');
  			continue;
  		}

  		for (var i = 0; i < metricsArr.length; i++) {
  			var metric = metricsArr[i];
  			var agg = metric.aggregate;
  			this.agents[agent] = this.agents[agent] || {};
  			this.agents[agent][queue] = this.agents[agent][queue] || {};
  			this.agents[agent][queue][state] = this.agents[agent][queue][state] || {};
  			this.agents[agent][queue][state][metric.value] = ga.getAggregate(metric.aggregate, metric.field);
  		}
  	}
  },

  _getAvgValuesFromAggregate: function(ga, metricsAvg, metricCreated) {
  	while (ga.next()) {
  		var agent = ga.getValue('agent');
  		var state = ga.getValue('state');
  		var queue = ga.getValue('queue');

  		if (JSUtil.nil(agent) || JSUtil.nil(state) || JSUtil.nil(queue)) {
  			gs.warn('ECCQueueStatsByECCAgent._getValuesFromAggregate() found nil values');
  			continue;
  		}
  		var sum_processed = ga.getAggregate('SUM', 'sys_updated_on');
  		var sum_created = ga.getAggregate('SUM', 'sys_created_on');
  		var count =  ga.getAggregate('COUNT', 'queue');
  		var processed_sum_age = new GlideDateTime(sum_processed);
  		var created_sum_age = new GlideDateTime(sum_created);
  		if (state == 'processed') {
  			if (sum_processed != null && sum_created != null) {
  				this.agents[agent][queue][state]['avg_age'] = (processed_sum_age.getNumericValue() - created_sum_age.getNumericValue())/count;
  			} else {
  				this.agents[agent][queue][state]['avg_age'] = 0;
  			}
  		}

  		if (state == 'ready') {
  			if (sum_created != null) {
  				this.agents[agent][queue][state]['avg_age'] = (new GlideDateTime().getNumericValue() - (created_sum_age.getNumericValue()/count));
  			} else {
  				this.agents[agent][queue][state]['avg_age'] = 0;
  			}
  		}
  	}
  },



  /***********************************************************************************************************/
  _updateOrInsertRecords: function() {
  	var agents = Object.keys(this.agents);

  	for (var i=0; i < agents.length; i++) {
  		var agentName = agents[i];
  		// Skip if nil agent name
  		if (!agentName)
  			continue;

          var agentObj = this.agents[agentName];
          // Skip if data wasn't populated
  		if (!agentObj)
  			continue;

          var eccQueueStatsGr = new GlideRecord(this.TABLE_NAME__ECC_QUEUE_STATS_BY_ECC_AGENT);
          eccQueueStatsGr.addQuery('agent', agentName);
          eccQueueStatsGr.query();

          if (eccQueueStatsGr.next()) {
              this._updateRecord(eccQueueStatsGr, agentObj);
          } else {
              this._insertRecord(agentName, agentObj);
          }
      }
  },
  /***********************************************************************************************************/
  _updateRecord: function(eccQueueStatsGr, agentObj) {
  	this._lookupAndSetValuesOnGlideRecord(eccQueueStatsGr, agentObj);
  	eccQueueStatsGr.update();
  },
  /***********************************************************************************************************/
  _insertRecord: function(agentName, agentObj) {
  	var toInsert = new GlideRecord(this.TABLE_NAME__ECC_QUEUE_STATS_BY_ECC_AGENT);
  	toInsert.initialize();

  	toInsert.setValue('agent', agentName);
  	this._lookupAndSetValuesOnGlideRecord(toInsert, agentObj);

  	toInsert.insert();
  },

  _resetAverageData: function(eccQueueStatsGr) {
  	if (!eccQueueStatsGr)
  		return;
  	eccQueueStatsGr.setValue('avg_age_processed_input', this.avg_age_processed_input);
  	eccQueueStatsGr.setValue('avg_age_processed_output', this.avg_age_processed_output);
  	eccQueueStatsGr.setValue('avg_age_ready_input', this.avg_age_ready_input);
  	eccQueueStatsGr.setValue('avg_age_ready_output', this.avg_age_ready_output);
  	eccQueueStatsGr.setValue('current_count_ready_input', 0);
  	eccQueueStatsGr.setValue('current_count_ready_output', 0);
  	eccQueueStatsGr.setValue('current_count_processed_input', 0);
  	eccQueueStatsGr.setValue('current_count_processed_output', 0);
  	eccQueueStatsGr.setValue('current_count_processing_input', 0);
  	eccQueueStatsGr.setValue('current_count_processing_output', 0);
  	return eccQueueStatsGr;
  },

  /***********************************************************************************************************/
  _lookupAndSetValuesOnGlideRecord: function(eccQueueStatsGr, agentObj) {
  	eccQueueStatsGr.setValue('last_refreshed_date', this.new_last_refreshed_date);
  	eccQueueStatsGr.setValue('interval_duration_seconds', this.interval_duration_seconds);
  	eccQueueStatsGr = this._resetAverageData(eccQueueStatsGr);
  	for (queue in agentObj) {
  		for (state in agentObj[queue]) {
  			for (metric in agentObj[queue][state]) {
  				var val = agentObj[queue][state][metric];
  				eccQueueStatsGr.setValue('' + metric + '_' + state + '_' + queue, val);
  			}
  		}
  	}
  },
  /***********************************************************************************************************/

  type: 'ECCQueueStatsByECCAgent'
};

Sys ID

ab0192cab79c6300883bca11ee11a963

Offical Documentation

Official Docs: