blob: dec3049f932a2f892792664dfb55fd77ea255bef [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Licensed under the Apache License, Version 2.0
* See accompanying LICENSE file.
*/
angular.module('dashboard')
.config(['$stateProvider',
function ($stateProvider) {
'use strict';
$stateProvider
.state('streamingapp.dag', {
url: '/dag',
templateUrl: 'views/apps/streamingapp/dag.html',
controller: 'StreamingAppDagCtrl'
});
}])
.controller('StreamingAppDagCtrl', ['$scope', '$state', '$modal', '$contextmenu', '$visNetworkStyle',
function ($scope, $state, $modal, $contextmenu, $vis) {
'use strict';
$scope.activeProcessorId = -1;
$scope.processorActionMenu = [{
text: '<i class="glyphicon glyphicon-stats"></i> <b>View Details</b>',
click: 'view()'
}, {
text: '<i class="glyphicon glyphicon-none"></i> Change Parallelism...',
click: 'modify({parallelism:true})'
}, {
text: '<i class="glyphicon glyphicon-none"></i> Change More...',
click: 'modify()'
}];
var editorDialog = $modal({
templateUrl: "views/apps/streamingapp/popups/dag_edit.html",
backdrop: 'static',
show: false,
scope: $scope,
controller: 'StreamingAppDagEditCtrl'
});
$scope.view = function () {
var options = {processorId: $scope.activeProcessorId};
$state.go('streamingapp.processor', options);
};
$scope.modify = function (options) {
$scope.modifyOptions = options;
editorDialog.$promise.then(editorDialog.show);
};
$scope.visGraph = {
options: $vis.newHierarchicalLayoutOptions({depth: $scope.dag.hierarchyDepth()}),
data: $vis.newData(),
events: {
onDoubleClick: function (data) {
if (data.nodes.length === 1) {
var processorId = Number(data.nodes[0]);
$state.go('streamingapp.processor', {processorId: processorId});
}
},
onContext: function (data) {
if (data.hasOwnProperty('node')) {
$scope.activeProcessorId = Number(data.node);
$scope.$apply();
showProcessorOperationsContextMenu(data.pointer.DOM);
}
},
onSelectNode: function (data) {
if (data.nodes.length === 1) {
$scope.activeProcessorId = Number(data.nodes[0]);
$scope.$apply();
}
},
onDeselectNode: function () {
$scope.activeProcessorId = -1;
$scope.$apply();
},
onHoverNode: function () {
$('html,body').css('cursor', 'pointer');
},
onBlurNode: function () {
$('html,body').css('cursor', 'default');
}
}
};
function showProcessorOperationsContextMenu(position) {
var elem = document.getElementById('dag-node-menu');
$contextmenu.popup(elem, position);
}
/** Scope independent draw methods */
var draw = {
removeDeadElements: function (visDataSet, aliveElementIds) {
var ids = visDataSet.getIds().filter(function (id) {
return !aliveElementIds.hasOwnProperty(id);
});
if (ids.length) {
// Batch removal is faster
visDataSet.remove(ids);
}
},
updateVisGraphNodes: function (visNodes, data) {
data.processorWeights[-1] = 0; // weight range from 0 to max weight
var suggestRadiusFn = draw._rangeMapper(data.processorWeights, $vis.nodeRadiusRange());
var diff = [];
_.forEach(data.processors, function (processor) {
var label = $vis.processorNameAsLabel(processor);
var weight = data.processorWeights[processor.id];
var visNode = visNodes.get(processor.id);
var size = d3.round(suggestRadiusFn(weight), 1);
var concern = data.processorStallingTasks.hasOwnProperty(processor.id);
var color = $vis.nodeColor(concern);
if (!visNode || visNode.label !== label || visNode.size !== size ||
(visNode.color && visNode.color !== color)) {
diff.push({
id: processor.id, level: processor.hierarchy, // once created, the hierarchy will not change
label: label,
size: size,
color: color
});
}
});
if (diff.length) {
visNodes.update(diff);
}
},
updateVisGraphEdges: function (visEdges, data) {
data.edgeBandwidths[-1] = 0; // minimal bandwidth
var suggestEdgeWidthFn = draw._rangeMapper(data.edgeBandwidths, $vis.edgeWidthRange());
var suggestEdgeArrowSizeFn = draw._rangeMapper(data.edgeBandwidths, $vis.edgeArrowSizeRange());
var suggestEdgeOpacityFn = draw._rangeMapper(data.edgeBandwidths, $vis.edgeOpacityRange());
var diff = [];
_.forEach(data.edges, function (edge, edgeId) {
var bandwidth = data.edgeBandwidths[edgeId];
var visEdge = visEdges.get(edgeId);
var width = d3.round(suggestEdgeWidthFn(bandwidth), 1);
if (!visEdge || visEdge.width !== width) {
diff.push({
id: edgeId, from: edge.from, to: edge.to,
width: width,
hoverWidth: 0 /*delta*/,
selectionWidth: 0 /*delta*/,
arrows: {
to: {scaleFactor: d3.round(suggestEdgeArrowSizeFn(bandwidth), 1)}
},
color: angular.merge({
opacity: d3.round(suggestEdgeOpacityFn(bandwidth), 1)
}, $vis.edgeColorSet(bandwidth > 0)
)
});
}
});
if (diff.length) {
visEdges.update(diff);
}
},
_rangeMapper: function (dict, range) {
var values = d3.values(dict);
return d3.scale.linear().domain(d3.extent(values)).range(range);
}
};
function redrawGraph(dagData) {
var visNodes = $scope.visGraph.data.nodes;
var visEdges = $scope.visGraph.data.edges;
visNodes.setOptions({queue: true});
visEdges.setOptions({queue: true});
try {
draw.removeDeadElements(visNodes, dagData.processors);
draw.removeDeadElements(visEdges, dagData.edges);
draw.updateVisGraphNodes(visNodes, dagData);
draw.updateVisGraphEdges(visEdges, dagData);
} finally {
visNodes.setOptions({queue: false});
visEdges.setOptions({queue: false});
}
}
function updateMetricsValues(metricsProvider) {
var receivedMessages = metricsProvider.getSinkProcessorReceivedMessageTotalAndRate();
var sentMessages = metricsProvider.getSourceProcessorSentMessageTotalAndRate();
$scope.currentMessageSendRate = sentMessages.rate;
$scope.currentMessageReceiveRate = receivedMessages.rate;
$scope.totalSentMessages = sentMessages.total;
$scope.totalReceivedMessages = receivedMessages.total;
$scope.criticalPathLatency = metricsProvider.getCriticalPathLatency();
}
$scope.$watchCollection('dag.metricsUpdateTime', function () {
redrawGraph($scope.dag.getWeightedDagView());
updateMetricsValues($scope.dag);
});
}])
;