Add the build worker and job count information to the charts
This commit is contained in:
parent
63cd6ffcc3
commit
d359c849cd
9 changed files with 134 additions and 9 deletions
|
@ -8,3 +8,6 @@ class BaseComponent(ApplicationSession):
|
||||||
self.parent_manager = None
|
self.parent_manager = None
|
||||||
self.build_logs = None
|
self.build_logs = None
|
||||||
self.user_files = None
|
self.user_files = None
|
||||||
|
|
||||||
|
def kind(self):
|
||||||
|
raise NotImplementedError
|
|
@ -49,6 +49,9 @@ class BuildComponent(BaseComponent):
|
||||||
|
|
||||||
BaseComponent.__init__(self, config, **kwargs)
|
BaseComponent.__init__(self, config, **kwargs)
|
||||||
|
|
||||||
|
def kind(self):
|
||||||
|
return 'builder'
|
||||||
|
|
||||||
def onConnect(self):
|
def onConnect(self):
|
||||||
self.join(self.builder_realm)
|
self.join(self.builder_realm)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,9 @@ logger = logging.getLogger(__name__)
|
||||||
class DynamicRegistrationComponent(BaseComponent):
|
class DynamicRegistrationComponent(BaseComponent):
|
||||||
""" Component session that handles dynamic registration of the builder components. """
|
""" Component session that handles dynamic registration of the builder components. """
|
||||||
|
|
||||||
|
def kind(self):
|
||||||
|
return 'registration'
|
||||||
|
|
||||||
def onConnect(self):
|
def onConnect(self):
|
||||||
self.join(REGISTRATION_REALM)
|
self.join(REGISTRATION_REALM)
|
||||||
|
|
||||||
|
@ -69,6 +72,7 @@ class EnterpriseManager(BaseManager):
|
||||||
|
|
||||||
def build_component_disposed(self, build_component, timed_out):
|
def build_component_disposed(self, build_component, timed_out):
|
||||||
self.build_components.remove(build_component)
|
self.build_components.remove(build_component)
|
||||||
|
self.unregister_component(build_component)
|
||||||
|
|
||||||
def num_workers(self):
|
def num_workers(self):
|
||||||
return len(self.build_components)
|
return len(self.build_components)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import trollius
|
import trollius
|
||||||
|
import json
|
||||||
|
|
||||||
from autobahn.asyncio.wamp import RouterFactory, RouterSessionFactory
|
from autobahn.asyncio.wamp import RouterFactory, RouterSessionFactory
|
||||||
from autobahn.asyncio.websocket import WampWebSocketServerFactory
|
from autobahn.asyncio.websocket import WampWebSocketServerFactory
|
||||||
|
@ -63,7 +64,20 @@ class BuilderServer(object):
|
||||||
|
|
||||||
@controller_app.route('/status')
|
@controller_app.route('/status')
|
||||||
def status():
|
def status():
|
||||||
return server._current_status
|
(running_count, available_count) = server._queue.get_metrics()
|
||||||
|
|
||||||
|
workers = [component for component in server._current_components
|
||||||
|
if component.kind() == 'builder']
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'status': server._current_status,
|
||||||
|
'running_local': server._job_count,
|
||||||
|
'running_total': running_count,
|
||||||
|
'workers': len(workers),
|
||||||
|
'job_total': available_count
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.dumps(data)
|
||||||
|
|
||||||
self._controller_app = controller_app
|
self._controller_app = controller_app
|
||||||
|
|
||||||
|
|
|
@ -41,10 +41,7 @@ class WorkQueue(object):
|
||||||
def _name_match_query(self):
|
def _name_match_query(self):
|
||||||
return '%s%%' % self._canonical_name([self._queue_name] + self._canonical_name_match_list)
|
return '%s%%' % self._canonical_name([self._queue_name] + self._canonical_name_match_list)
|
||||||
|
|
||||||
def update_metrics(self):
|
def get_metrics(self):
|
||||||
if self._reporter is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
with self._transaction_factory(db):
|
with self._transaction_factory(db):
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
name_match_query = self._name_match_query()
|
name_match_query = self._name_match_query()
|
||||||
|
@ -52,9 +49,16 @@ class WorkQueue(object):
|
||||||
running_query = self._running_jobs(now, name_match_query)
|
running_query = self._running_jobs(now, name_match_query)
|
||||||
running_count = running_query.distinct().count()
|
running_count = running_query.distinct().count()
|
||||||
|
|
||||||
avialable_query = self._available_jobs(now, name_match_query, running_query)
|
available_query = self._available_jobs(now, name_match_query, running_query)
|
||||||
available_count = avialable_query.select(QueueItem.queue_name).distinct().count()
|
available_count = available_query.select(QueueItem.queue_name).distinct().count()
|
||||||
|
|
||||||
|
return (running_count, available_count)
|
||||||
|
|
||||||
|
def update_metrics(self):
|
||||||
|
if self._reporter is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
(running_count, available_count) = self.get_metrics()
|
||||||
self._reporter(self._currently_processing, running_count, running_count + available_count)
|
self._reporter(self._currently_processing, running_count, running_count + available_count)
|
||||||
|
|
||||||
def put(self, canonical_name_list, message, available_after=0, retries_remaining=5):
|
def put(self, canonical_name_list, message, available_after=0, retries_remaining=5):
|
||||||
|
|
|
@ -5,7 +5,7 @@ from flask import request, Blueprint, abort, Response
|
||||||
from flask.ext.login import current_user
|
from flask.ext.login import current_user
|
||||||
from auth.auth import require_session_login
|
from auth.auth import require_session_login
|
||||||
from endpoints.common import route_show_if
|
from endpoints.common import route_show_if
|
||||||
from app import userevents
|
from app import app, userevents
|
||||||
from auth.permissions import SuperUserPermission
|
from auth.permissions import SuperUserPermission
|
||||||
|
|
||||||
import features
|
import features
|
||||||
|
@ -26,6 +26,11 @@ def ps():
|
||||||
|
|
||||||
def generator():
|
def generator():
|
||||||
while True:
|
while True:
|
||||||
|
builder_data = app.config['HTTPCLIENT'].get('http://localhost:8686/status', timeout=1)
|
||||||
|
build_status = {}
|
||||||
|
if builder_data.status_code == 200:
|
||||||
|
build_status = json.loads(builder_data.text)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'count': {
|
'count': {
|
||||||
'cpu': psutil.cpu_percent(interval=1, percpu=True),
|
'cpu': psutil.cpu_percent(interval=1, percpu=True),
|
||||||
|
@ -34,7 +39,8 @@ def ps():
|
||||||
'connections': len(psutil.net_connections()),
|
'connections': len(psutil.net_connections()),
|
||||||
'processes': len(psutil.pids()),
|
'processes': len(psutil.pids()),
|
||||||
'network': psutil.net_io_counters()
|
'network': psutil.net_io_counters()
|
||||||
}
|
},
|
||||||
|
'build': build_status
|
||||||
}
|
}
|
||||||
json_string = json.dumps(data)
|
json_string = json.dumps(data)
|
||||||
yield 'data: %s\n\n' % json_string
|
yield 'data: %s\n\n' % json_string
|
||||||
|
|
|
@ -1,4 +1,17 @@
|
||||||
<div class="ps-usage-graph-element">
|
<div class="ps-usage-graph-element">
|
||||||
|
Build Workers:
|
||||||
|
<div class="realtime-area-chart"
|
||||||
|
data="[data.build.job_total, data.build.running_total]"
|
||||||
|
labels="['Queued Build Jobs', 'Running Build Jobs']"
|
||||||
|
colors="['rgb(157, 194, 211)', 'rgb(56, 122, 163)']"
|
||||||
|
counter="counter"></div>
|
||||||
|
|
||||||
|
<div class="realtime-area-chart"
|
||||||
|
data="[data.build.job_total, data.build.workers, data.build.running_local]"
|
||||||
|
labels="['Queued Build Jobs', 'Build Workers (local)', 'Running Build Jobs (local)']"
|
||||||
|
colors="['rgb(157, 194, 211)', 'rgb(161, 208, 93)', 'rgb(210, 237, 130)']"
|
||||||
|
counter="counter"></div>
|
||||||
|
|
||||||
CPU:
|
CPU:
|
||||||
<div class="realtime-line-chart" data="data.count.cpu" counter="counter"
|
<div class="realtime-line-chart" data="data.count.cpu" counter="counter"
|
||||||
label-template="CPU #{x} %"></div>
|
label-template="CPU #{x} %"></div>
|
||||||
|
|
3
static/directives/realtime-area-chart.html
Normal file
3
static/directives/realtime-area-chart.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="realtime-area-chart-element">
|
||||||
|
<div class="chart" style="width: 450px; height: 250px;"></div>
|
||||||
|
</div>
|
|
@ -6522,6 +6522,81 @@ quayApp.directive('locationView', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
quayApp.directive('realtimeAreaChart', function () {
|
||||||
|
var directiveDefinitionObject = {
|
||||||
|
priority: 0,
|
||||||
|
templateUrl: '/static/directives/realtime-area-chart.html',
|
||||||
|
replace: false,
|
||||||
|
transclude: false,
|
||||||
|
restrict: 'C',
|
||||||
|
scope: {
|
||||||
|
'data': '=data',
|
||||||
|
'labels': '=labels',
|
||||||
|
'colors': '=colors',
|
||||||
|
'counter': '=counter'
|
||||||
|
},
|
||||||
|
controller: function($scope, $element) {
|
||||||
|
var graph = null;
|
||||||
|
var series = [];
|
||||||
|
var palette = new Rickshaw.Color.Palette( { scheme: 'spectrum14' } );
|
||||||
|
var colors = $scope.colors || [];
|
||||||
|
|
||||||
|
var setupGraph = function() {
|
||||||
|
for (var i = 0; i < $scope.labels.length; ++i) {
|
||||||
|
series.push({
|
||||||
|
name: $scope.labels[i],
|
||||||
|
color: i >= colors.length ? palette.color(): $scope.colors[i],
|
||||||
|
stroke: 'rgba(0,0,0,0.15)',
|
||||||
|
data: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
graph = new Rickshaw.Graph( {
|
||||||
|
element: $element.find('.chart')[0],
|
||||||
|
renderer: 'area',
|
||||||
|
stroke: true,
|
||||||
|
series: series,
|
||||||
|
min: 0
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var refresh = function(data) {
|
||||||
|
if (!data || $scope.counter < 0) { return; }
|
||||||
|
if (!graph) {
|
||||||
|
setupGraph();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < $scope.data.length; ++i) {
|
||||||
|
series[i].data.push(
|
||||||
|
{'x': $scope.counter, 'y': $scope.data[i] }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverDetail = new Rickshaw.Graph.HoverDetail({
|
||||||
|
graph: graph,
|
||||||
|
xFormatter: function(x) {
|
||||||
|
return x.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.renderer.unstack = true;
|
||||||
|
graph.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$watch('counter', function() {
|
||||||
|
refresh($scope.data_raw);
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$watch('data', function(data) {
|
||||||
|
$scope.data_raw = data;
|
||||||
|
refresh($scope.data_raw);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return directiveDefinitionObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
quayApp.directive('realtimeLineChart', function () {
|
quayApp.directive('realtimeLineChart', function () {
|
||||||
var directiveDefinitionObject = {
|
var directiveDefinitionObject = {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
|
|
Reference in a new issue