Commit b5f1362b authored by David Trudgian's avatar David Trudgian

Basic command group / comman functionality is working

parent b1cb8331
......@@ -13,14 +13,39 @@
textarea {
border: 1px solid #999999;
font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
font-size: 8px !important;
font-size: 10pt !important;
background-color: #333333 !important;
color: #eaeaea !important;
}
.fullheight{
.fullheight {
height: 100%;
}
ol.rungroup-list {
padding-left: 1em;
}
ol.rungroup-list > li {
padding: 0.5em;
margin-bottom: 1em;
background-color: #eeeeee;
}
ul.runcmd-list {
margin-top: 0.5em;
list-style: none;
padding-left: 0.5em;
}
ul.runcmd-list > li {
margin-bottom: 0.5em;
}
input.cmd {
font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
}
</style>
......@@ -40,7 +65,7 @@
<!-- EMBER APP -------------------------------------------------------------->
<script type="text/x-handlebars" data-template-name="index">
<script type="text/x-handlebars" data-template-name="script">
<div class="container">
<form class="form-horizontal">
......@@ -70,7 +95,8 @@
<div class="col-sm-8">
{{ input type="text" value=stdout size="50" class="form-control"
data-toggle="tooltip" data-placement="top"
title="File name for output from commands. %j will be replaced with the job's id when it runs."
title="File name for output from commands. %j will be replaced with the job's id
when it runs."
}}
</div>
</div>
......@@ -80,7 +106,8 @@
<div class="col-sm-8">
{{ input type="text" value=stderr size="50" class="form-control"
data-toggle="tooltip" data-placement="top"
title="File name for error output from commands. %j will be replaced with the job's id when it runs."
title="File name for error output from commands. %j will be replaced with the job's
id when it runs."
}}
</div>
</div>
......@@ -164,39 +191,15 @@
<div class="panel-body">
<p>The batch script will consist of one or more <em>command groups</em>, each containing one
or
more
<em>commands</em> that need to be run. If multiple commands are given in a step they
will
run in
parallel.
This allows efficient use of the nodes, which have 32 CPU threads. E.g. the first step
in
a batch script might run a pre-processing command on each of eight files, in parallel.
<p>The batch script contains one or more <em>groups</em> of <em>commands</em>.
If multiple commands are given in a group they will run in parallel. This allows
efficient use of the nodes, which have 32 CPU threads.
E.g. the first step in a batch script might run a pre-processing command on each of
eight files, in parallel.
</p>
</p>
<ol>
{{#each runGroup in model.runGroups}}
<li>Run the following commands in parallel and wait until complete: {{ @index }}
<ul>
{{#each runCmd in runGroup.runCmds}}
<li>
{{ input type="text" size="50" value=runCmd.cmd }}
<button {{ action "removeRunCmd" }}>REMOVE</button>
</li>
{{/each}}
<li><i class="glyphicon glyphicon-plus"></i> Add a command to this step</li>
</ul>
</li>
{{/each}}
<li><i class="glyphicon glyphicon-plus"></i> Add a step</li>
</ol>
{{ render "run-groups" runGroups }}
</div>
......@@ -206,6 +209,7 @@
</div>
<div class="row">
<div class="col-md-12">
......@@ -229,7 +233,10 @@
</div>
<div class="form-group">
{{ input type="submit" value="Submit job to cluster" class="btn btn-primary"}}
<button type="submit" class="btn btn-primary btn-lg btn-block"><i
class="glyphicon glyphicon-thumbs-up"></i>
Submit Job To Cluster
</button>
</div>
......@@ -245,8 +252,41 @@
</div>
</script>
<script type="text/x-handlebars" data-template-name="run-groups">
<ol class="rungroup-list">
{{#each runGroup in model itemController="run-group" }}
<li>Run commands in parallel and wait until complete <a {{ action "removeRunGroup" }}><i
class="glyphicon glyphicon-trash"></i></a>
<ul class="runcmd-list">
{{ render "run-cmds" runGroup.runCmds }}
</ul>
{{/each}}
</ol>
<a {{ action "addRunGroup" }}><i class="glyphicon glyphicon-plus"></i> Add a new group of commands</a>
</script>
<script type="text/x-handlebars" data-template-name="run-cmds">
{{#each runCmd in model itemController="run-cmd" }}
<li>
{{ input type="text" class="cmd" size="50" value=runCmd.cmd }}
<a {{ action "removeRunCmd" }}><i class="glyphicon glyphicon-trash"></i></a>
</li>
{{/each}}
<li><a {{ action "addRunCmd" }}><i class="glyphicon glyphicon-plus"></i> Add a new command to this group</a></li>
</script>
<!-- EMBER IMPORTS ----------------------------------------------------------->
<script src="bower_components/jquery/dist/jquery.js"></script>
......@@ -257,11 +297,13 @@
<script src="bower_components/ember-validations-nocli/dist/ember-validations.js"></script>
<script src="js/app.js"></script>
<script src="js/models/sbatch.js"></script>
<script src="js/models/script.js"></script>
<script src="js/models/partition.js"></script>
<script src="js/models/rungroup.js"></script>
<script src="js/models/runcmd.js"></script>
<script src="js/controllers/sbatch_controller.js"></script>
<script src="js/controllers/script_controller.js"></script>
<script src="js/controllers/rungroups_controller.js"></script>
<script src="js/controllers/runcmds_controller.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
......
/*global Ember, DS, Todos:true */
App = Ember.Application.create();
/*global Ember, DS, App */
App = Ember.Application.create({LOG_TRANSITIONS: true});
App.ApplicationAdapter = DS.FixtureAdapter;
App.ApplicationRoute = Ember.Route.extend({
model: function(){
// Create the batch script object
sbatch = this.store.createRecord('sbatch');
App.Router.map(function() {
this.route('script');
});
// Add a single command group as an example
runGroup1 = this.store.createRecord('run-group');
runGroup2 = this.store.createRecord('run-group');
// Run 'hostname' in this command group
runGroup1.get('runCmds').addObject( this.store.createRecord('run-cmd', { cmd: 'hostname' }) );
runGroup1.get('runCmds').addObject( this.store.createRecord('run-cmd', { cmd: 'date' }) );
runGroup1.get('runCmds').addObject( this.store.createRecord('run-cmd', { cmd: 'ps' }) );
App.IndexRoute = Ember.Route.extend({
beforeModel: function() {
this.transitionTo('script');
}
});
runGroup2.get('runCmds').addObject( this.store.createRecord('run-cmd', { cmd: 'hostname' }) );
runGroup2.get('runCmds').addObject( this.store.createRecord('run-cmd', { cmd: 'date' }) );
runGroup2.get('runCmds').addObject( this.store.createRecord('run-cmd', { cmd: 'ps' }) );
// Add the command group to the batch script
sbatch.get('runGroups').addObject( runGroup1 );
sbatch.get('runGroups').addObject( runGroup2 );
App.ScriptRoute = Ember.Route.extend({
return sbatch;
model: function () {
}
'use strict';
// Create the batch script object
var sbatch = this.store.createRecord('script');
// Add a single command group as an example
var runGroup1 = this.store.createRecord('run-group');
});
// Run 'hostname' in this command group
runGroup1.get('runCmds').addObject(this.store.createRecord('run-cmd', {cmd: 'hostname'}));
/**
A replacement for #each that provides an index value (and other helpful values) for each iteration.
Unless using `foo in bar` format, the item at each iteration will be accessible via the `item` variable.
Simple Example
--------------
```
{{#eachIndexed bar in foo}}
{{index}} - {{bar}}
{{/#eachIndexed}}
```
Helpful iteration values
------------------------
* index: The current iteration index (zero indexed)
* index_1: The current iteration index (one indexed)
* first: True if this is the first item in the list
* last: True if this is the last item in the list
* even: True if it's an even iteration (0, 2, 4, 6)
* odd: True if it's an odd iteration (1, 3, 5)
*/
Ember.Handlebars.registerHelper('eachIndexed', function eachHelper(path, options) {
var keywordName = 'item',
fn;
// Process arguments (either #earchIndexed bar, or #earchIndexed foo in bar)
if (arguments.length === 4) {
Ember.assert('If you pass more than one argument to the eachIndexed helper, it must be in the form #eachIndexed foo in bar', arguments[1] === 'in');
Ember.assert(arguments[0] +' is a reserved word in #eachIndexed', $.inArray(arguments[0], ['index', 'index+1', 'even', 'odd']));
keywordName = arguments[0];
options = arguments[3];
path = arguments[2];
options.hash.keyword = keywordName;
if (path === '') { path = 'this'; }
}
// Add the command group to the batch script
sbatch.get('runGroups').addObject(runGroup1);
if (arguments.length === 1) {
options = path;
path = 'this';
}
return sbatch;
},
});
// Wrap the callback function in our own that sets the index value
fn = options.fn;
function eachFn(){
var keywords = arguments[1].data.keywords,
view = arguments[1].data.view,
index = view.contentIndex,
list = view._parentView.get('content') || [],
len = list.length;
// Set indexes
keywords['index'] = index;
keywords['index_1'] = index + 1;
keywords['first'] = (index === 0);
keywords['last'] = (index + 1 === len);
keywords['even'] = (index % 2 === 0);
keywords['odd'] = !keywords['even'];
arguments[1].data.keywords = keywords;
return fn.apply(this, arguments);
}
options.fn = eachFn;
// Render
options.hash.dataSourceBinding = path;
if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) {
new Ember.Handlebars.GroupedEach(this, path, options).render();
} else {
return Ember.Handlebars.helpers.collection.call(this, 'Ember.Handlebars.EachView', options);
}
});
\ No newline at end of file
(function () {
'use strict';
App.IndexController = Ember.ObjectController.extend({
actions: {
removeRunCmd: function () {
var runCmd = this.get('model');
runCmd.deleteRecord();
runCmd.save();
},
},
partitions: function() {
return this.get('store').find('partition');
}.property(),
partitionChanged: function() {
this.set('memGB', null);
}.observes('partition'),
// Hard-coded Options for mail type
optionMailTypes: [
{ id: null, description: 'Never'},
{ id: 'ALL', description: 'All status changes'}
],
optionNodes: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ],
// Job Limit Fields - Options & Selections
optionDays: [0, 1, 2, 3, 4, 5, 6, 7],
optionHours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
optionMinutes: [0, 15, 30, 45],
selectedDays: 0,
selectedHours: 2,
selectedMinutes: 0,
timeLimitChanged: function () {
this.get('model').set('timeLimit', this.get('selectedDays') + '-' + this.get('selectedHours') + ':' + this.get('selectedMinutes') + ':00');
}.observes('selectedDays', 'selectedHours', 'selectedMinutes' ),
memGB: null,
memGBChanged: function(){
this.get('model').set('mem', this.get('memGB') * 1024);
}.observes('memGB'),
});
})();
/*global App, DS */
(function () {
'use strict';
......@@ -6,7 +7,7 @@
memoryGB: DS.attr('number'),
cores: DS.attr('boolean'),
resources: DS.attr('string'),
sbatch: DS.hasMany('sbatch'),
sbatch: DS.hasMany('script'),
selectOption: function () {
return this.get('id') + ' - ' + this.get('description');
......@@ -30,11 +31,7 @@
return options;
}.property('memoryGB'),
maxMemory: function () {
return this.get('memoryGB') - 2;
}
}.property('memoryGB')
});
......
/*global App, DS */
(function () {
'use strict';
......@@ -11,7 +12,7 @@
return this.get('cmd');
}.property('cmd'),
}.property('cmd')
......
/*global Todos, DS */
/*global App, DS */
(function () {
'use strict';
App.RunGroup = DS.Model.extend({
sbatch: DS.belongsTo('sbatch'),
sbatch: DS.belongsTo('script'),
runCmds: DS.hasMany('run-cmd'),
cmdString: function () {
var output = '';
var cmdCount = this.get('runCmds').length;
this.get('runCmds').forEach(function (runCmd) {
var validCmds = this.get('runCmds').filter(function(item,index,self){
if ( item.get('cmdString') ) { return true };
});
var cmdCount = validCmds.length;
validCmds.forEach(function (runCmd) {
output += runCmd.get('cmdString');
if( cmdCount > 1 )
if( cmdCount > 1 ) {
output += ' &';
}
output += "\n";
......@@ -34,7 +40,7 @@
}.property('runCmds.@each.cmdString')
})
});
})();
/*global Todos, DS */
(function () {
'use strict';
App.Sbatch = DS.Model.extend({
// -p
partition: DS.belongsTo('partition'),
// -N
nodes: DS.attr('number', { defaultValue: 1 }),
// --mem
mem: DS.attr('number'),
// -t
timeLimit: DS.attr('string', {defaultValue: '0-2:0:0'} ),
// -o
stdout: DS.attr('string', {defaultValue: 'job_%j.out'} ),
// -e
stderr: DS.attr('string', {defaultValue: 'job_%j.err'} ),
// --mail-type
mailType: DS.attr('string', {defaultValue: 'ALL'} ),
// --mail-user
mailUser: DS.attr('string'),
// --job-name
jobName: DS.attr('string', {defaultValue: 'NewJob'} ),
// --ntasks-per-node
nTasksPerNode: DS.attr('number', {defaultValue: null }),
runGroups: DS.hasMany('run-group'),
script: function(){
var script_lines = new Array();
script_lines.push('#');
script_lines.push('# CREATED USING THE BIOHPC PORTAL on ' + Date().toString() );
script_lines.push('#');
script_lines.push('# This file is batch script used to run commands on the BioHPC cluster.');
script_lines.push('# The script is submitted to the cluster using the SLURM `sbatch` command.')
script_lines.push('# Lines starting with # are comments, and will not be run.');
script_lines.push('# Lines starting with #SBATCH specify options for the scheduler.');
script_lines.push('# Lines that do not start with # or #SBATCH are commands that will run.');
script_lines.push('');
script_lines.push('# Name for the job that will be visible in the job queue and accounting tools.');
script_lines.push('#SBATCH --job-name ' + this.get('jobName') );
script_lines.push('');
if (this.get('partition')) {
script_lines.push('# Name of the SLURM partition that this job should run on.');
script_lines.push('#SBATCH -p ' + this.get('partition').id + ' # partition (queue)');
}
script_lines.push('# Number of nodes required to run this job');
script_lines.push('#SBATCH -N ' + this.get('nodes') );
script_lines.push('');
if (this.get('nTasksPerNode')) {
script_lines.push('# When using srun or mpirun, start n tasks on each node.');
script_lines.push('#SBATCH --ntasks-per-node ' + this.get('nTasksPerNode') + ' # tasks per node');
script_lines.push('');
}
if (this.get('mem')) {
script_lines.push('# Memory (RAM) requirement/limit in MB.');
script_lines.push('#SBATCH --mem ' + this.get('mem') + ' # Memory Requirement (MB)');
script_lines.push('');
}
script_lines.push('# Time limit for the job in the format Days-H:M:S');
script_lines.push('# A job that reaches its time limit will be cancelled.');
script_lines.push('# Specify an accurate time limit for efficient scheduling so your job runs promptly.');
script_lines.push('#SBATCH -t ' + this.get('timeLimit'));
script_lines.push('')
script_lines.push('# The standard output and errors from commands will be written to these files.');
script_lines.push('# %j in the filename will be replace with the job number when it is submitted.');
script_lines.push('#SBATCH -o ' + this.get('stdout') );
script_lines.push('#SBATCH -e ' + this.get('stderr') );
script_lines.push('')
if (this.get('mailType') ) {
script_lines.push('# Send an email when the job status changes, to the specfied address.');
script_lines.push('#SBATCH --mail-type ' + this.get('mailType'));
script_lines.push('#SBATCH --mail-user ' + this.get('mailUser'));
script_lines.push('')
}
var groupCount = 1;
this.get('runGroups').forEach(function (runGroup) {
script_lines.push("# COMMAND GROUP " + groupCount );
script_lines.push("# Commands run in parallel, and we wait for all commands to finish before proceeding.")
script_lines.push(runGroup.get('cmdString') );
groupCount ++;
});
script_lines.push("");
script_lines.push("# END OF SCRIPT");
script_lines.push("");
return script_lines.join('\n');
}.property('partition', 'nodes', 'mem','timeLimit','jobName', 'stderr', 'stdout', 'mailType','mailUser','nTasksPerNode', 'runGroups.@each.cmdString')
});
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment