333 lines
14 KiB
HTML
333 lines
14 KiB
HTML
<!doctype html>
|
|
<html ng-app="app">
|
|
|
|
<head>
|
|
<style>
|
|
.trace {
|
|
min-width: 900px;
|
|
width: 100%;
|
|
}
|
|
|
|
.trace tr:hover {
|
|
background-color: #D9EDF7 !important;
|
|
}
|
|
|
|
.trace tr td {
|
|
width: 14%;
|
|
white-space: nowrap;
|
|
padding: 2px;
|
|
border-right: 1px solid #EEE;
|
|
}
|
|
|
|
.trace tr td.details {
|
|
width: 10%;
|
|
padding-right: 20px;
|
|
}
|
|
|
|
.trace.cursor_pointer_on_hover {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.trace .level {
|
|
width: 10%;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.bold {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.duration {
|
|
width: 25px;
|
|
margin: 0px;
|
|
padding: 0px;
|
|
background-color: #C6EFF3;
|
|
border-radius: 4px;
|
|
font-size: 10px;
|
|
}
|
|
|
|
.duration div {
|
|
padding-top: 4px;
|
|
padding-bottom: 4px;
|
|
text-align: center;
|
|
}
|
|
|
|
dl {
|
|
margin: 5px 0px;
|
|
}
|
|
|
|
.hljs {
|
|
white-space: pre;
|
|
word-wrap: normal;
|
|
}
|
|
|
|
.hljs.wrapped {
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.toggle-button {
|
|
margin-top: -7px;
|
|
}
|
|
|
|
</style>
|
|
<script>
|
|
var static_files = $LOCAL;
|
|
if (static_files){
|
|
document.write('<link rel="stylesheet" href="/libs/bootstrap.min.css">');
|
|
document.write('<link rel="stylesheet" href="/libs/github.min.css">');
|
|
document.write('<script type="text/javascript" src="/libs/angular.min.js"><\/script>');
|
|
document.write('<script type="text/javascript" src="/libs/ui-bootstrap-tpls-2.3.1.min.js"><\/script>');
|
|
document.write('<script type="text/javascript" src="/libs/highlight.min.js"><\/script>');
|
|
document.write('<script type="text/javascript" src="/libs/angular-highlightjs.min.js"><\/script>');
|
|
document.write('<script type="text/javascript" src="/libs/angular-ui-tree.min.js"><\/script>');
|
|
document.write('<script type="text/javascript" src="/libs/angular-ui-tree-filter.js"><\/script>');
|
|
}
|
|
else{
|
|
document.write('<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">');
|
|
document.write('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.8.0/styles/github.min.css">');
|
|
document.write('<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.min.js"><\/script>');
|
|
document.write('<script type="text/javascript" src="https://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-2.3.1.min.js"><\/script>');
|
|
document.write('<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.8.0/highlight.min.js"><\/script>');
|
|
document.write('<script type="text/javascript" src="https://pc035860.github.io/angular-highlightjs/angular-highlightjs.min.js"><\/script>');
|
|
document.write('<script type="text/javascript" src="https://cdn.rawgit.com/angular-ui-tree/angular-ui-tree/master/dist/angular-ui-tree.min.js"><\/script>');
|
|
document.write('<script type="text/javascript" src="https://cdn.rawgit.com/EE/angular-ui-tree-filter/master/dist/angular-ui-tree-filter.js"><\/script>');
|
|
}
|
|
</script>
|
|
</head>
|
|
|
|
<body>
|
|
<script>
|
|
(function(angular) {
|
|
'use strict';
|
|
|
|
var OSProfilerData = $DATA;
|
|
|
|
angular
|
|
.module('app', ['ui.bootstrap', 'hljs', 'ui.tree', 'ui.tree-filter'])
|
|
.config(['$rootScopeProvider', function ($rootScopeProvider) {
|
|
$rootScopeProvider.digestTtl(50);
|
|
}])
|
|
.config(['hljsServiceProvider', function (hljsServiceProvider) {
|
|
hljsServiceProvider.setOptions({
|
|
// replace tab with 4 spaces
|
|
tabReplace: ' '
|
|
});
|
|
}])
|
|
.config(['uiTreeFilterSettingsProvider', function (uiTreeFilterSettingsProvider) {
|
|
uiTreeFilterSettingsProvider.addresses = [
|
|
'info.name',
|
|
'info.project',
|
|
'info.service',
|
|
'info.host'
|
|
];
|
|
uiTreeFilterSettingsProvider.descendantCollection = 'children';
|
|
}])
|
|
.controller('ProfilerController', ProfilerController)
|
|
.controller('ModalInstanceController', ModalInstanceController);
|
|
|
|
// Inject services
|
|
ProfilerController.$inject = ['$filter', '$uibModal'];
|
|
ModalInstanceController.$inject = ['$uibModalInstance', 'info'];
|
|
|
|
function ProfilerController($filter, $uibModal) {
|
|
// NOTE(tovin07): Bind this to vm. This is controller as and vm syntax.
|
|
// This style is mainstream now. It replaces $scope style.
|
|
// Ref: https://johnpapa.net/angularjss-controller-as-and-the-vm-variable/
|
|
var vm = this;
|
|
|
|
vm.filter = $filter('uiTreeFilter');
|
|
|
|
var converInput = function(input, level) {
|
|
level = (level) ? level : 0;
|
|
input.level = level;
|
|
input.is_leaf = !input.children.length;
|
|
|
|
for (var i = 0; i < input.children.length; i++) {
|
|
converInput(input.children[i], level + 1);
|
|
}
|
|
return input;
|
|
};
|
|
|
|
vm.isLastTrace = function(started) {
|
|
if (started >=0 && started == vm.tree[0].info.last_trace_started) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
vm.getWidth = function(data) {
|
|
var fullDuration = vm.tree[0].info.finished;
|
|
var duration = (data.info.finished - data.info.started) * 100.0 / fullDuration;
|
|
return (duration >= 0.5) ? duration : 0.5;
|
|
};
|
|
|
|
vm.getStarted = function(data) {
|
|
var fullDuration = vm.tree[0].info.finished;
|
|
return data.info.started * 100.0 / fullDuration;
|
|
};
|
|
|
|
vm.isImportance = function(data) {
|
|
return ['total', 'wsgi', 'rpc'].indexOf(data.info.name) != -1;
|
|
};
|
|
|
|
vm.display = function(data) {
|
|
$uibModal.open({
|
|
animation: true,
|
|
templateUrl: 'modal_renderer.html',
|
|
controller: 'ModalInstanceController',
|
|
controllerAs: 'modal',
|
|
size: 'lg',
|
|
resolve: {
|
|
info: function() {
|
|
return angular.copy(data.info);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
vm.tree = [converInput(OSProfilerData)];
|
|
}
|
|
|
|
function ModalInstanceController($uibModalInstance, info){
|
|
var modal = this;
|
|
var metadata = {};
|
|
angular.forEach(info, function(value, key) {
|
|
var parts = key.split('.');
|
|
var metaText = 'meta';
|
|
if (parts[0] == metaText) {
|
|
if (parts.length == 2) {
|
|
this[parts[1]] = value;
|
|
} else {
|
|
var groupName = parts[1];
|
|
if (!(groupName in this)) {
|
|
this[groupName] = {};
|
|
}
|
|
// Plus 2 for 2 dots such as: meta.raw_payload.heat.wsgi-start
|
|
var index = metaText.length + parts[1].length + 2;
|
|
this[groupName][key.slice(index)] = value;
|
|
}
|
|
};
|
|
}, metadata);
|
|
|
|
info.duration = info.finished - info.started;
|
|
// Escape single-quotes to prevent angular parse lexerr
|
|
info.metadata = JSON.stringify(metadata, null, 4).replace(/'/g, "\\'");
|
|
|
|
// Bind to view model
|
|
modal.info = info;
|
|
|
|
modal.columns = [
|
|
['name', 'project', 'service', 'host'],
|
|
['started', 'finished', 'duration', 'exception']
|
|
];
|
|
|
|
modal.toggleWrap = function() {
|
|
var element = angular.element(document.querySelector('code.hljs'));
|
|
|
|
var wrappedClass = 'wrapped';
|
|
var isWrapped = element.hasClass(wrappedClass);
|
|
if (isWrapped) {
|
|
element.removeClass(wrappedClass);
|
|
} else {
|
|
element.addClass(wrappedClass);
|
|
}
|
|
};
|
|
|
|
modal.close = function() {
|
|
$uibModalInstance.dismiss('close');
|
|
};
|
|
}
|
|
})(window.angular);
|
|
</script>
|
|
<!--Tree item template-->
|
|
<script type="text/ng-template" id="tree_item_renderer.html">
|
|
<div ng-init="hide_children=false">
|
|
<table class="trace cursor_pointer_on_hover">
|
|
<tr ng-class="{'bg-success': vm.isLastTrace(data.info.started)}">
|
|
<td class="level" style="padding-left: {{::data.level * 5}}px;">
|
|
<button type="button" class="btn btn-default btn-xs" ng-disabled="data.is_leaf" ng-click="hide_children=!hide_children">
|
|
<span class="glyphicon glyphicon-{{(data.is_leaf) ? 'cloud' : ((hide_children) ? 'plus': 'minus')}}"></span>
|
|
{{::data.level || 0}}
|
|
</button>
|
|
</td>
|
|
<td ng-click="vm.display(data)" class="text-center">
|
|
<div class="duration" style="width: {{vm.getWidth(data)}}%; margin-left: {{vm.getStarted(data)}}%;">
|
|
<div>{{data.info.finished - data.info.started}} ms</div>
|
|
</div>
|
|
</td>
|
|
<td ng-click="vm.display(data)" class="{{vm.isImportance(data) ? 'bold' : ''}} text-right">{{::data.info.name}}</td>
|
|
<td ng-click="vm.display(data)">{{::data.info.project || "n/a"}}</td>
|
|
<td ng-click="vm.display(data)">{{::data.info.service || "n/a"}}</td>
|
|
<td ng-click="vm.display(data)">{{::data.info.host || "n/a"}}</td>
|
|
<td class="details">
|
|
<a href="#" ng-click="vm.display(data)">Details</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<div ui-tree-nodes ng-model="data.children" ng-hide="hide_children">
|
|
<div ui-tree-node ng-repeat="data in data.children" ng-include="'tree_item_renderer.html'" ng-hide="!vm.filter(data, vm.filterPattern)"></div>
|
|
</div>
|
|
</div>
|
|
</script>
|
|
<!--Modal template-->
|
|
<script type="text/ng-template" id="modal_renderer.html">
|
|
<div class="modal-header">
|
|
<h3 class="text-center">
|
|
<strong>Trace Point Details</strong>
|
|
<span class="btn btn-default pull-right toggle-button" ng-click="modal.toggleWrap()">Toggle wrap-text</span>
|
|
</h3>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="row">
|
|
<div class="col-md-6" ng-repeat="cols in modal.columns">
|
|
<dl class="dl-horizontal" ng-repeat="column in cols">
|
|
<dt class="text-capitalize">{{::column}}</dt>
|
|
<dd>{{::modal.info[column]}}</dd>
|
|
</dl>
|
|
</div>
|
|
<div class="col-md-12">
|
|
<!--For metadata only-->
|
|
<dl class="dl-horizontal">
|
|
<dt class="text-capitalize">metadata</dt>
|
|
<dd hljs hljs-language="json" hljs-source="'{{::modal.info.metadata}}'"></dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<span class="btn btn-default" ng-click="modal.close()">Close</span>
|
|
</div>
|
|
</script>
|
|
<!--Body-->
|
|
<div ng-controller="ProfilerController as vm">
|
|
<form class="form-horizontal">
|
|
<div class="form-group">
|
|
<label for="filterPattern" class="col-md-4 control-label">Span filtering:</label>
|
|
<div class="col-md-6">
|
|
<input type="text" class="form-control" id="filterPattern"
|
|
placeholder="Input your regular expression pattern here"
|
|
ng-model="vm.filterPattern">
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<table class="trace">
|
|
<tr class="bold text-left" style="border-bottom: solid 1px gray;">
|
|
<td class="level">Levels</td>
|
|
<td>Duration</td>
|
|
<td class="text-right">Type</td>
|
|
<td>Project</td>
|
|
<td>Service</td>
|
|
<td>Host</td>
|
|
<td class="details">Details</td>
|
|
</tr>
|
|
</table>
|
|
<div ui-tree>
|
|
<div ui-tree-nodes ng-model="vm.tree">
|
|
<div ui-tree-node ng-repeat="data in vm.tree" ng-include="'tree_item_renderer.html'" ng-hide="!vm.filter(data, vm.filterPattern)"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
|
|
</html>
|