UNPKG

strong-arc

Version:

A visual suite for the StrongLoop API Platform

614 lines (552 loc) 19.3 kB
Metrics.controller('MetricsMainController', [ '$scope', '$state', '$log', 'growl', '$interval', '$timeout', 'MetricsService', 'PMPidService', 'PMHostService', 'ChartConfigService', 'ArcNavigationService', function($scope, $state, $log, growl, $interval, $timeout, MetricsService, PMPidService, PMHostService, ChartConfigService, ArcNavigationService) { $scope.selectedPMHost = {}; $scope.isToggleHostChange = false; $scope.updateHost = function(host) { $scope.selectedPMHost = host; $scope.currentServerConfig = host; $scope.isToggleHostChange = !$scope.isToggleHostChange; }; $scope.isDisplayChartValid = false; // control display of charts (transition between data sets) $scope.currentServerConfig = PMHostService.getLatestPMServer(function(host) { $scope.currentServerConfig = host; $scope.selectedPMHost = host; }); $scope.isCollapsed = true; // settings $scope.maxInitDataPointThreshold = MetricsService.getMaxInitDataPointThreshold(); // limit on the initial data load $scope.maxDataPointThrottle = MetricsService.getMaxDataPointThrottle(); // max number of current data points $scope.metricsUpdateInterval = MetricsService.getMetricsUpdateInterval(); $scope.currentPMServerName = ''; // hybrid host / port as unique id metric branch $scope.currentWorkerId; $scope.currentProcessId; $scope.activeProcess = null; $scope.isTimerRunning = false; $scope.isBackground = false; $scope.isMetricsLoaded = false; $scope.dataPointCount = 0; $scope.lastGoodTS; // fallback $scope.breakLoop = false; $scope.currentStub; // temporary dataset to drive the current context $scope.lastTimeStamp = {}; // object collection of metrics servers and the last metrics received $scope.currentMetrics = []; // the root $scope.sysTime = { ticker:0 }; var ONE_SECOND = 1000; // counter timer between metrics requested var updateInterval = null; var activeProcess = null; $scope.chartData = []; $scope.updateHost = function(host) { $scope.host = host; $scope.selectedPMHost = host; $scope.currentServerConfig = host; $scope.isToggleHostChange = !$scope.isToggleHostChange; }; $scope.updateProcesses = function(processes) { $scope.processes = processes; if (processes.map && processes.length > 0) { $scope.updateProcessSelection([processes[0]]); } }; $scope.updateProcessSelection = function(selection) { if (selection.length) { selection[0].isActive = true; $scope.activeProcess = selection[0]; } }; /* * Query filter helpers * * */ // test for existing timestamp on given server stub function doesTimeStampExist() { if (!$scope.lastTimeStamp) { return false; } if (!$scope.lastTimeStamp[$scope.currentPMServerName]) { return false; } if (!$scope.lastTimeStamp[$scope.currentPMServerName][$scope.currentProcessId]) { return false; } return true; } // get last query time if it exists function getQueryDate() { if(!doesTimeStampExist()) { return; } return $scope.lastTimeStamp[$scope.currentPMServerName][$scope.currentProcessId]; } // metric filter function getMetricFilter() { var filter = {where:{ processId: $scope.currentProcessId, workerId: $scope.currentWorkerId }}; var queryDate = getQueryDate(); if (queryDate) { filter.where.timeStamp = { gt: queryDate}; } return filter; } function getMetrics() { if ($scope.isTimerRunning){ getChartData(getMetricFilter()); } } $scope.showMetrics = function(){ return $scope.isMetricsLoaded; }; /* * * Trim metrics arrays to keep data point count under control * * */ function ensureThrottledMetric(metricStub, newMetric) { if (!Array.isArray(metricStub)) { metricStub = []; } metricStub.push(newMetric); var cLength = metricStub.length; // check if current count is higher than the threshold if (cLength > $scope.maxDataPointThrottle) { var cutoffPoint = (cLength - $scope.maxDataPointThrottle); // if so then slice the array metricStub = metricStub.slice(cutoffPoint); } return metricStub; } /* * * PROCESS THE RAW METRICS OFF THE TICK * * - could be an initial dump of server memory * - or just a 15 second update * * */ function processMetricsTick(metrics) { var mStub; // placeholder for unique server/service metrics 'bucket' //iterate over the returned results to normalize the data metrics.map(function(metric) { var processedMetric = { timeStamp: metric.timeStamp, processId: metric.processId, cpu: { total:metric.gauges[METRICS_CONST.CPU_TOTAL], system:metric.gauges[METRICS_CONST.CPU_SYSTEM], user:metric.gauges[METRICS_CONST.CPU_USER] }, heap: { total:metric.gauges[METRICS_CONST.HEAP_TOTAL], used:metric.gauges[METRICS_CONST.HEAP_USED] }, loop: { minimum:metric.gauges[METRICS_CONST.LOOP_MIN], maximum:metric.gauges[METRICS_CONST.LOOP_MAX], average:metric.gauges[METRICS_CONST.LOOP_AVG] }, loopCount: { count:metric.counters[METRICS_CONST.LOOP_COUNT] }, http: { minimum:metric.gauges[METRICS_CONST.HTTP_MIN], maximum:metric.gauges[METRICS_CONST.HTTP_MAX], average:metric.gauges[METRICS_CONST.HTTP_AVG] }, httpCount: { count:metric.counters[METRICS_CONST.HTTP_COUNT] }, /* * * TIERS * * */ mongodb: { minimum:metric.gauges[METRICS_CONST.MONGO_MIN], maximum:metric.gauges[METRICS_CONST.MONGO_MAX], average:metric.gauges[METRICS_CONST.MONGO_AVG] }, mysql: { minimum:metric.gauges[METRICS_CONST.MYSQL_MIN], maximum:metric.gauges[METRICS_CONST.MYSQL_MAX], average:metric.gauges[METRICS_CONST.MYSQL_AVG] }, redis: { minimum:metric.gauges[METRICS_CONST.REDIS_MIN], maximum:metric.gauges[METRICS_CONST.REDIS_MAX], average:metric.gauges[METRICS_CONST.REDIS_AVG] }, dao: { // dao minimum:metric.gauges[METRICS_CONST.DAO_MIN], maximum:metric.gauges[METRICS_CONST.DAO_MAX], average:metric.gauges[METRICS_CONST.DAO_AVG] }, leveldown: { // leveldown minimum:metric.gauges[METRICS_CONST.LEVELDOWN_MIN], maximum:metric.gauges[METRICS_CONST.LEVELDOWN_MAX], average:metric.gauges[METRICS_CONST.LEVELDOWN_AVG] }, postgres: { // postgres minimum:metric.gauges[METRICS_CONST.POSTGRES_MIN], maximum:metric.gauges[METRICS_CONST.POSTGRES_MAX], average:metric.gauges[METRICS_CONST.POSTGRES_AVG] }, oracle: { // oracle minimum:metric.gauges[METRICS_CONST.ORACLE_MIN], maximum:metric.gauges[METRICS_CONST.ORACLE_MAX], average:metric.gauges[METRICS_CONST.ORACLE_AVG] }, riak: { // riak minimum:metric.gauges[METRICS_CONST.RIAK_MIN], maximum:metric.gauges[METRICS_CONST.RIAK_MAX], average:metric.gauges[METRICS_CONST.RIAK_AVG] }, memcached: { minimum:metric.gauges[METRICS_CONST.MEMCACHED_MIN], maximum:metric.gauges[METRICS_CONST.MEMCACHED_MAX], average:metric.gauges[METRICS_CONST.MEMCACHED_AVG] }, counters: { memcached:metric.counters[METRICS_CONST.MEMCACHED_COUNT], redis:metric.counters[METRICS_CONST.REDIS_COUNT], mysql:metric.counters[METRICS_CONST.MYSQL_COUNT], mongodb:metric.counters[METRICS_CONST.MONGO_COUNT], dao:metric.counters[METRICS_CONST.DAO_COUNT], leveldown:metric.counters[METRICS_CONST.LEVELDOWN_COUNT], postgres:metric.counters[METRICS_CONST.POSTGRES_COUNT], riak:metric.counters[METRICS_CONST.RIAK_COUNT], oracle:metric.counters[METRICS_CONST.ORACLE_COUNT], loop:metric.counters[METRICS_CONST.LOOP_COUNT], http:metric.counters[METRICS_CONST.HTTP_COUNT] } }; /* * * build the unique metric stub instance * - unique server/port/processId object instance * - track individual metrics as timestamped arrays off * this object * - eg: mStub['loop']['average'].push(newMetric); * * */ $scope.currentProcessId = metric.processId; // check if the host and port have a metrics stub if (!$scope.currentMetrics[$scope.currentPMServerName]) { $scope.currentMetrics[$scope.currentPMServerName] = {}; } if (!$scope.currentMetrics[$scope.currentPMServerName][$scope.currentProcessId]) { $scope.currentMetrics[$scope.currentPMServerName][$scope.currentProcessId] = {}; } // short reference to unique metric 'stub' instance $scope.currentStub = $scope.currentMetrics[$scope.currentPMServerName][$scope.currentProcessId]; /* * metrics type/name/group * * * * for each 'data' i.e metric group eg cpu * * iterate over the metrics collection * * * set each metric group * * * */ $scope.chartData.map(function(chart) { /* * data.name * data.displayName * data.metrics [] * * */ // each metric collection ex chart if (!$scope.currentStub[chart.name]) { $scope.currentStub[chart.name] = {}; } chart.metrics.map(function(metric) { if (!processedMetric[chart.name][metric.name]){ //$log.warn('no metric: ' + chart.name + ':' + metric.name); return; } var yVal = processedMetric[chart.name][metric.name]; if (yVal !== undefined) { $scope.currentStub[chart.name][METRICS_CONST[metric.constant]] = ensureThrottledMetric($scope.currentStub[chart.name][METRICS_CONST[metric.constant]], {x: new Date(processedMetric.timeStamp), y: yVal}); } }); }); // last timestamp used for next query filter if (!$scope.lastTimeStamp[$scope.currentPMServerName]) { $scope.lastTimeStamp[$scope.currentPMServerName] = {}; } $scope.lastTimeStamp[$scope.currentPMServerName][$scope.currentProcessId] = processedMetric.timeStamp; }); if ($scope.currentStub) { ChartConfigService.clearChartMemory(); if (!$scope.isBackground) { renderTheCharts(); } } // data point count $scope.dataPointCount = $scope.currentStub.cpu[METRICS_CONST.CPU_TOTAL].length; } function renderTheCharts() { $scope.readyCharts = true; $timeout(function() { // assign scope chart model variable data here $scope.chartData.map(function(chart) { var data = $scope.currentStub[chart.name]; $scope[chart.chartConfig] = ChartConfigService.getChartOptions(chart.chartOptions); $scope[chart.chartModel] = ChartConfigService.getChartMetricsData(chart, data); }); $scope.readyCharts = false; }, 0); } /* * * Raw data request * * - filter config * - server config * * * Request the data for a period of time based ona * - workerId * - processId * * * raw request may return too many records so a throttle * check is made before passing off to the metrics data * processor * * */ function getChartData(filter) { if ($scope.isValidProcess()) { MetricsService.getMetricsSnapShot($scope.currentServerConfig, filter) .then(function(rawMetrics) { var metricsToRender = rawMetrics; // reset everything to 0 until timestamp issue is fixed if (rawMetrics && rawMetrics.length) { $scope.lastGoodTS = rawMetrics[rawMetrics.length -1].timeStamp; $scope.breakLoop = false; $scope.isMetricsLoaded = true; var rawMetricsCount = rawMetrics.length; if (rawMetricsCount > $scope.maxInitDataPointThreshold) { var startPoint = ((rawMetricsCount - 1) - $scope.maxInitDataPointThreshold); metricsToRender = rawMetrics.slice(startPoint); } processMetricsTick(metricsToRender); } else { $log.warn('getMetricsSnapShot returned no metrics '); var pmTimestamp = $scope.lastTimeStamp[$scope.currentPMServerName]; if (pmTimestamp === undefined) { pmTimestamp = []; $scope.lastTimeStamp[$scope.currentPMServerName] = pmTimestamp; } pmTimestamp[$scope.currentProcessId] = $scope.lastGoodTS; if (!$scope.breakLoop) { getMetrics(); $scope.breakLoop = true; } return; } }); } else { $log.warn('no metrics'); } } $scope.getChartOptions = function(configRef) { return $scope[configRef]; }; $scope.getChartModel = function(modelRef) { return $scope[modelRef]; }; $scope.showChart = function(chart, modelRef) { if ($scope.readyCharts) { return true; } var data = $scope[modelRef]; if (data) { return data.some(function(x) { return x.values && x.values.length > 0; }); } return false; }; /* * * * check if current view is valid - is there a processId and a workerId * * * */ $scope.isValidProcess = function() { if (($scope.currentProcessId && $scope.currentProcessId > -1) && ($scope.currentWorkerId > -1)) { return true; } return false; }; $scope.resetCharts = function() { if ($scope.isValidProcess()) { $scope.restartTicker(); getMetrics(); } }; /* * * CHART CONTROLS - ACTIVE INDICATOR CIRCLE * * */ $scope.getChartControlItemColor = function(item) { var x = item; var styleString = ''; if (item.active) { styleString = ('border: 1px solid ' + item.color + ';' + 'background-color: ' + item.color); return styleString; } else { styleString = 'border:1px solid #aaaaaa;background-color: #555555;'; return styleString; } }; /* * * HTML * * CHART CONTROL * * * */ $scope.toggleChartMetric = function(chartMetric) { ChartConfigService.toggleMetricStatus(chartMetric); renderTheCharts(); }; $scope.startTicker = function() { $scope.sysTime.ticker = $scope.metricsUpdateInterval; $scope.startTimer(); }; $scope.restartTicker = function() { $scope.sysTime.ticker = $scope.metricsUpdateInterval; $scope.startTimer(); }; $scope.startTimer = function() { // cancel the previous interval if it wasn't cleaned up $scope.stopTimer(); updateInterval = $interval(function() { $scope.sysTime.ticker--; if ($scope.sysTime.ticker < 1) { $scope.sysTime.ticker = $scope.metricsUpdateInterval; getMetrics(); } }, ONE_SECOND); $scope.isTimerRunning = true; }; $scope.stopTimer = function() { if (updateInterval !== null) { $interval.cancel(updateInterval); updateInterval = null; $scope.isTimerRunning = false; } }; $scope.sleepUpdates = function() { $scope.isBackground = true; }; $scope.wakeUpdates = function() { $scope.isBackground = false; renderTheCharts(); }; // Prevent the metrics updating if the window isn't visisble. ArcNavigationService.visibilityChange($scope.sleepUpdates.bind($scope), $scope.wakeUpdates.bind($scope)); /* * * WATCHES * * */ $scope.$watch('activeProcess', function(newVal) { $scope.cpuChartModel = []; $scope.loopChartModel = []; $scope.heapChartModel = []; if (newVal && newVal.id) { $scope.currentWorkerId = newVal.workerId; $scope.currentProcessId = newVal.id; $scope.currentPMServerName = $scope.currentServerConfig.host + ':' + $scope.currentServerConfig.port; $scope.isDisplayChartValid = true; $scope.resetCharts(); } else { $scope.stopTimer(); } }); $scope.$watch('processes', function(newVal) { if (newVal && newVal.length < 1) { $scope.isDisplayChartValid = false; } }); $scope.$watch('maxInitDataPointThreshold', function(newVal) { if (newVal) { $scope.maxInitDataPointThreshold = MetricsService.setMaxInitDataPointThreshold(parseInt(newVal)); } }); $scope.$watch('maxDataPointThrottle', function(newVal) { if (newVal) { $scope.maxDataPointThrottle = MetricsService.setMaxDataPointThrottle(parseInt(newVal)); } }); $scope.$watch('metricsUpdateInterval', function(newVal) { if (newVal) { $scope.metricsUpdateInterval = MetricsService.setMetricsUpdateInterval(parseInt(newVal)); } }); $scope.$watch('currentServerConfig', function(newVal, oldVal) { // do a quick metrics request and clear the data $scope.currentWorkerId = ''; $scope.currentProcessId = ''; $scope.init(); }, true); $scope.init = function() { // check if user has a valid metrics license MetricsService.validateLicense() .then(function(isValid) { if (!isValid) { $log.warn('metrics license is not valid'); return; // was having some trouble with this return - hence the else clause } else { $scope.chartData = ChartConfigService.initChartConfigData() .then(function(response) { $scope.chartData = response; }); // replace with dynamic config if ($scope.isValidProcess()) { $scope.currentMetrics = []; getMetrics(); $scope.startTicker(); } else { $scope.stopTimer(); } } }) .catch(function(error) { $log.warn('exception validating metrics license (controller)'); }); }; $scope.init(); } ]);