Skip to content
Snippets Groups Projects
Commit b62a7d21 authored by André Anjos's avatar André Anjos :speech_balloon:
Browse files

[backend] Implement poor-man's scheduler & worker

parent c4a5d831
No related branches found
No related tags found
1 merge request!194Scheduler
Pipeline #
......@@ -37,15 +37,58 @@
<div class="row">
<div class="col-sm-12">
<div class="alert alert-success" role="alert" style="text-align: center;">
<i class="fa fa-clock-o fa-2x vertical-center"></i> Updated: {% now "H:i O, jS F Y" %}
<i class="fa fa-clock-o fa-2x vertical-center"></i> Updated: {% now "H:i:s O, jS F Y" %}
<div class="pull-right action-buttons">
<a id="update-workers-button" class="btn btn-default btn-info" data-toggle="tooltip" data-placement="bottom" title="Force all workers to update their state" href="{% url 'backend:update-workers' %}"><i class="fa fa-times"></i> Update Workers</a>
<a id="update-workers-button" class="btn btn-default btn-info" data-toggle="tooltip" data-placement="bottom" title="Request all workers to update their state when possible" href="{% url 'backend:update-workers' %}"><i class="fa fa-gears"></i> Update Workers</a>
<a id="cancel-experiments-button" class="btn btn-default btn-delete" data-toggle="tooltip" data-placement="bottom" title="Cancel all running experiments" href="{% url 'backend:cancel-experiments' %}"><i class="fa fa-times"></i> Cancel Experiments</a>
</div>
</div>
</div>
</div>
{% if helper_panel %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">Helper Panel</h3>
</div>
<div class="panel-body">
<p class="help">Use this panel to <strong>locally</strong> launch scheduling activity. This functionality is intended as a <em>test</em> scheduler and worker replacement that can be used to run local experiments or debug. <strong>Don't use this in a production system.</strong> Every time you launch an activity, the page will reload to trigger this action. Scheduling happens in the context of the Django server running on the background. Worker processes are managed using subprocesses and don't block the web server.</p>
<div class="form-inline">
<div id="activity-group" class="form-group">
<label class="sr-only" for="activity">Activity</label>
<select id="activity" class="form-control">
<option value="both">Schedule &amp; Work</option>
<option value="schedule">Schedule</option>
<option value="work">Work</option>
</select>
</div>
<div id="periodically-group" class="form-group">
<div class="checkbox">
<label>
<input id="periodically" type="checkbox" checked="checked"> periodically
</label>
</div>
</div>
<div id="period-group" class="form-group">
<label class="sr-only" for="period">Period</label>
<div class="input-group">
<div class="input-group-addon">every</div>
<input type="text" class="form-control" id="period" value="{{ scheduling_period }}">
<div class="input-group-addon">s</div>
</div>
</div>
<button id="start" type="submit" class="btn btn-success"><i class="fa fa-play"></i> <span id="start">Start</span></button>
<button id="stop" type="submit" class="btn btn-danger disabled"><i class="fa fa-stop"></i> <span id="stop">Stop</span></button>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<div class="row">
<div class="col-sm-12">
......@@ -88,7 +131,6 @@
<canvas id="cache-chart" style="width: 80%; height: auto;"></canvas>
<div id="cache-legend" class="chart-legend"></div>
</div>
</div>
<!-- Experiments tab -->
......@@ -101,6 +143,7 @@
<th>Name</th>
<th>Blocks/Jobs</th>
<th>Job Splits</th>
<th>Assigned</th>
<th>Running</th>
<th>Completed</th>
<th>Failed</th>
......@@ -114,6 +157,7 @@
<td><a href="{{ obj.get_admin_change_url }}">{{ obj.fullname }}</a></td>
<td>{{ obj.blocks.count }}</td>
<td>{{ obj|count_job_splits }}</td>
<td>{{ obj|count_job_splits:"A" }}</td>
<td>{{ obj|count_job_splits:"P" }}</td>
<td>{{ obj|count_job_splits:"C" }}</td>
<td>{{ obj|count_job_splits:"F" }}</td>
......@@ -212,6 +256,10 @@
<script type="text/javascript">
$(document).ready(function() {
/**
* This bit of code here is to manage the normal parts of the scheduler
* page, like the tags and the cache chart which is displayed.
*/
manage_tabs('ul#object-tabs');
var data = {{ cache_chart_data|safe }};
......@@ -235,6 +283,98 @@ $(document).ready(function() {
display_cache_chart();
});
{% if helper_panel %}
/**
* This bit of code here is to manage helper panel, only included if that
* shows up.
*/
function display_periodic(period) {
$("input#periodically").prop("checked", true);
$("#period-group").show();
$("button#start > span#start").text("Start");
$("button#stop > span#stop").text("Stop");
$("button#stop").show();
$("input#period").val(period);
$("button#stop").disable();
}
function display_single_shot() {
$("input#periodically").prop("checked", false);
$("#period-group").hide();
$("button#start > span#start").text("Go");
$("button#stop > span#stop").text("Reset");
$("button#start").enable();
$("button#stop").enable();
}
/* controls button display */
$("input#periodically").click(function() {
if($(this).is(":checked")) {
display_periodic({{ scheduling_period }});
} else {
display_single_shot();
}
});
/* get url parameters */
function get_parameters() {
var vars = {};
var parts = location.search.replace(/[?&]+([^=&]+)=([^&]*)/gi,
function(m,key,value) {
vars[key] = value;
});
return vars;
}
/* controls the initial appearance */
var params = get_parameters();
if (location.search !== "") {
if ("period" in params) {
var period = parseInt(params.period, 10);
display_periodic(period);
$("button#start").disable();
$("button#stop").enable();
$("select#activity").disable();
$("input#period").disable();
$("input#periodically").disable();
setTimeout("location.reload(true);", period * 1000);
}
else {
display_single_shot();
}
if ("activity" in params) {
$("select#activity").val(params.activity);
}
} else {
display_periodic({{ scheduling_period }});
$("button#start").enable();
$("button#stop").disable();
$("select#activity").enable();
$("input#period").enable();
$("input#periodically").enable();
}
/* controls form submission buttons */
$("button#start").click(function() {
var params = '?activity=' + $("select#activity").val();
if ($("input#periodically").is(":checked")) {
params += '&period=' + $("input#period").val();
}
if (location.search === params) {
location.reload(true);
}
else {
location.search = params;
}
});
$("button#stop").click(function() {
location.search = "";
});
{% endif %}
});
</script>
{% endblock %}
......@@ -29,6 +29,8 @@
from django import template
from django.contrib.auth.models import User
from ..models import Job
register = template.Library()
......@@ -86,9 +88,6 @@ def visible_queues(context, object):
@register.filter
def count_job_splits(xp, status=None):
"""Returns job splits for an experiment in a certain state"""
if status == 'A':
return xp.job_splits(status=Job.QUEUED).filter(worker__isnull=False).count()
return xp.job_splits(status=status).count()
#--------------------------------------------------
......@@ -25,7 +25,7 @@
# #
###############################################################################
import collections
import os
import simplejson
from django.http import Http404, HttpResponseRedirect
......@@ -38,9 +38,37 @@ from django.contrib.auth.decorators import login_required
from django.http import HttpResponseForbidden
from django.contrib import messages
from beat.core.async import resolve_cpulimit_path
from .models import Environment, Worker, Queue
from ..experiments.models import Experiment
from . import state
from .schedule import schedule, work, worker_update
from .schedule import find_environments, resolve_process_path
#------------------------------------------------
class Work:
'''Helper to do the required worker job for local scheduling'''
cpulimit = resolve_cpulimit_path(None)
process = resolve_process_path()
environments = find_environments(None)
django_settings = os.environ.get('DJANGO_SETTINGS_MODULE',
'beat.web.settings.zzz')
def __call__(self):
# update workers that require updates
worker_update()
work(
Work.environments,
Work.cpulimit,
Work.process,
Work.django_settings,
)
#------------------------------------------------
......@@ -70,6 +98,16 @@ def scheduler(request):
cache_gb = int(cache['capacity-in-megabytes'] / 1024.0)
# do scheduling and/or worker activity if required
if request.GET.has_key('activity'):
activity = request.GET['activity']
if activity in ('both', 'schedule'):
schedule()
if activity in ('both', 'work'):
Work()()
return render_to_response('backend/scheduler.html',
dict(
jobs=state.jobs(),
......@@ -79,6 +117,7 @@ def scheduler(request):
cache_chart_data=simplejson.dumps(cache_chart_data),
cache_gb=cache_gb,
helper_panel=getattr(settings, 'SCHEDULING_PANEL', False),
scheduling_period=getattr(settings, 'SCHEDULING_INTERVAL', 5),
),
context_instance=RequestContext(request))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment