Merge remote-tracking branch 'origin/master' into statistics

This commit is contained in:
Maximilian Grundke
2015-11-19 16:01:53 +01:00
8 changed files with 76 additions and 34 deletions

View File

@ -2,6 +2,8 @@ $(function() {
var CHART_START = window.vis ? vis.moment().add(-1, 'minute') : undefined; var CHART_START = window.vis ? vis.moment().add(-1, 'minute') : undefined;
var DEFAULT_REFRESH_INTERVAL = 5000; var DEFAULT_REFRESH_INTERVAL = 5000;
var refreshInterval;
var dataset; var dataset;
var graph; var graph;
var groups; var groups;
@ -46,6 +48,9 @@ $(function() {
}; };
var refreshData = function(callback) { var refreshData = function(callback) {
if (! $.isController('dashboard')) {
clearInterval(refreshInterval);
} else {
var jqxhr = $.ajax({ var jqxhr = $.ajax({
dataType: 'json', dataType: 'json',
method: 'GET' method: 'GET'
@ -57,6 +62,7 @@ $(function() {
updateTable(response); updateTable(response);
requestAnimationFrame(refreshChart); requestAnimationFrame(refreshChart);
}); });
}
}; };
var setGroupVisibility = function(response) { var setGroupVisibility = function(response) {
@ -101,6 +107,7 @@ $(function() {
initializeChart(); initializeChart();
refreshData(); refreshData();
var refresh_interval = location.search.match(/interval=(\d+)/) ? parseInt(RegExp.$1) : DEFAULT_REFRESH_INTERVAL; var refresh_interval = location.search.match(/interval=(\d+)/) ? parseInt(RegExp.$1) : DEFAULT_REFRESH_INTERVAL;
setInterval(refreshData, refresh_interval); refreshInterval = setInterval(refreshData, refresh_interval);
} }
}); });

View File

@ -25,6 +25,11 @@ class SubmissionsController < ApplicationController
create_and_respond(object: @submission) create_and_respond(object: @submission)
end end
def command_substitutions(filename)
{class_name: File.basename(filename, File.extname(filename)).camelize, filename: filename, module_name: File.basename(filename, File.extname(filename)).underscore}
end
private :command_substitutions
def copy_comments def copy_comments
# copy each annotation and set the target_file.id # copy each annotation and set the target_file.id
unless(params[:annotations_arr].nil?) unless(params[:annotations_arr].nil?)
@ -88,6 +93,11 @@ class SubmissionsController < ApplicationController
# end # end
hijack do |tubesock| hijack do |tubesock|
# probably add:
# ensure
# #guarantee that the thread is releasing the DB connection after it is done
# ActiveRecord::Base.connectionpool.releaseconnection
# end
Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive? Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive?
@ -103,7 +113,7 @@ class SubmissionsController < ApplicationController
socket = result[:socket] socket = result[:socket]
socket.on :message do |event| socket.on :message do |event|
Rails.logger.info("Docker sending: " + event.data) Rails.logger.info( Time.now.getutc.to_s + ": Docker sending: " + event.data)
handle_message(event.data, tubesock) handle_message(event.data, tubesock)
end end
@ -112,7 +122,7 @@ class SubmissionsController < ApplicationController
end end
tubesock.onmessage do |data| tubesock.onmessage do |data|
Rails.logger.debug("Client sending: " + data) Rails.logger.info(Time.now.getutc.to_s + ": Client sending: " + data)
# Check wether the client send a JSON command and kill container # Check wether the client send a JSON command and kill container
# if the command is 'exit', send it to docker otherwise. # if the command is 'exit', send it to docker otherwise.
begin begin
@ -122,9 +132,11 @@ class SubmissionsController < ApplicationController
@docker_client.exit_container(result[:container]) @docker_client.exit_container(result[:container])
else else
socket.send data socket.send data
Rails.logger.debug('Sent the received client data to docker:' + data)
end end
rescue JSON::ParserError rescue JSON::ParserError
socket.send data socket.send data
Rails.logger.debug('Rescued parsing error, sent the received client data to docker:' + data)
end end
end end
else else
@ -145,8 +157,8 @@ class SubmissionsController < ApplicationController
kill_socket(tubesock) kill_socket(tubesock)
else else
# Filter out information about run_command, test_command, user or working directory # Filter out information about run_command, test_command, user or working directory
run_command = @submission.execution_environment.run_command run_command = @submission.execution_environment.run_command % command_substitutions(params[:filename])
test_command = @submission.execution_environment.test_command test_command = @submission.execution_environment.test_command % command_substitutions(params[:filename])
if !(/root|workspace|#{run_command}|#{test_command}/.match(message)) if !(/root|workspace|#{run_command}|#{test_command}/.match(message))
parse_message(message, 'stdout', tubesock) parse_message(message, 'stdout', tubesock)
end end
@ -157,6 +169,7 @@ class SubmissionsController < ApplicationController
begin begin
parsed = JSON.parse(message) parsed = JSON.parse(message)
socket.send_data message socket.send_data message
Rails.logger.info('parse_message sent: ' + message)
rescue JSON::ParserError => e rescue JSON::ParserError => e
# Check wether the message contains multiple lines, if true try to parse each line # Check wether the message contains multiple lines, if true try to parse each line
if ((recursive == true) && (message.include? "\n")) if ((recursive == true) && (message.include? "\n"))
@ -166,6 +179,7 @@ class SubmissionsController < ApplicationController
else else
parsed = {'cmd'=>'write','stream'=>output_stream,'data'=>message} parsed = {'cmd'=>'write','stream'=>output_stream,'data'=>message}
socket.send_data JSON.dump(parsed) socket.send_data JSON.dump(parsed)
Rails.logger.info('parse_message sent: ' + JSON.dump(parsed))
end end
end end
end end

View File

@ -6,6 +6,7 @@ h1 = ExecutionEnvironment.model_name.human(count: 2)
tr tr
th = t('activerecord.attributes.execution_environment.name') th = t('activerecord.attributes.execution_environment.name')
th = t('activerecord.attributes.execution_environment.user') th = t('activerecord.attributes.execution_environment.user')
th = t('activerecord.attributes.execution_environment.pool_size')
th = t('activerecord.attributes.execution_environment.memory_limit') th = t('activerecord.attributes.execution_environment.memory_limit')
th = t('activerecord.attributes.execution_environment.network_enabled') th = t('activerecord.attributes.execution_environment.network_enabled')
th = t('activerecord.attributes.execution_environment.permitted_execution_time') th = t('activerecord.attributes.execution_environment.permitted_execution_time')
@ -16,6 +17,7 @@ h1 = ExecutionEnvironment.model_name.human(count: 2)
tr tr
td = execution_environment.name td = execution_environment.name
td = link_to(execution_environment.author, execution_environment.author) td = link_to(execution_environment.author, execution_environment.author)
td = execution_environment.pool_size
td = execution_environment.memory_limit td = execution_environment.memory_limit
td = symbol_for(execution_environment.network_enabled) td = symbol_for(execution_environment.network_enabled)
td = execution_environment.permitted_execution_time td = execution_environment.permitted_execution_time

View File

@ -4,7 +4,7 @@
span.badge.pull-right.score span.badge.pull-right.score
p.lead = @exercise.description p.lead = render_markdown(@exercise.description)
#alert.alert.alert-danger role='alert' #alert.alert.alert-danger role='alert'
h4 = t('.alert.title') h4 = t('.alert.title')

View File

@ -4,6 +4,14 @@
<% <%
user = @request_for_comment.user user = @request_for_comment.user
submission_id = self.class.connection.execute("select id from submissions
where exercise_id =
#{@request_for_comment.exercise_id} AND
user_id = #{@request_for_comment.user_id} AND
#{@request_for_comment.user_id} > created_at
order by created_at desc
limit 1").first['id'].to_i
submission = Submission.find(submission_id)
%> %>
<%= user %> | <%= @request_for_comment.requested_at %> <%= user %> | <%= @request_for_comment.requested_at %>

View File

@ -300,7 +300,7 @@ de:
failure: Fehlerhafte E-Mail oder Passwort. failure: Fehlerhafte E-Mail oder Passwort.
success: Sie haben sich erfolgreich angemeldet. success: Sie haben sich erfolgreich angemeldet.
create_through_lti: create_through_lti:
session_with_outcome: 'Nachdem Sie die Aufgabe bearbeitet haben, wird Ihre Bewertung an %{consumer} übermittelt.' session_with_outcome: 'Bitte beachten Sie, dass zur Gutschrift der Punkte Ihr Code nach der Bearbeitung durch Klicken auf den Button "Code zur Bewertung abgeben" eingetragen werden muss.'
session_without_outcome: 'Dies ist eine Übungs-Session. Ihre Bewertung wird nicht an %{consumer} übermittelt.' session_without_outcome: 'Dies ist eine Übungs-Session. Ihre Bewertung wird nicht an %{consumer} übermittelt.'
destroy: destroy:
link: Abmelden link: Abmelden

View File

@ -300,7 +300,7 @@ en:
failure: Invalid email or password. failure: Invalid email or password.
success: Successfully signed in. success: Successfully signed in.
create_through_lti: create_through_lti:
session_with_outcome: 'After you have finished the exercise, your grade will be transmitted to %{consumer}.' session_with_outcome: 'Please click "Submit Code for Assessment" after scoring to send your score %{consumer}.'
session_without_outcome: 'This is a practice session. Your grade will not be transmitted to %{consumer}.' session_without_outcome: 'This is a practice session. Your grade will not be transmitted to %{consumer}.'
destroy: destroy:
link: Sign out link: Sign out

View File

@ -162,6 +162,7 @@ class DockerClient
@socket ||= create_socket(@container) @socket ||= create_socket(@container)
# Newline required to flush # Newline required to flush
@socket.send command + "\n" @socket.send command + "\n"
Rails.logger.info('Sent command: ' + command.to_s)
{status: :container_running, socket: @socket, container: @container} {status: :container_running, socket: @socket, container: @container}
else else
{status: :container_depleted} {status: :container_depleted}
@ -173,6 +174,7 @@ class DockerClient
We need to start a second thread to kill the websocket connection, We need to start a second thread to kill the websocket connection,
as it is impossible to determine whether further input is requested. as it is impossible to determine whether further input is requested.
""" """
#begin
@thread = Thread.new do @thread = Thread.new do
timeout = @execution_environment.permitted_execution_time.to_i # seconds timeout = @execution_environment.permitted_execution_time.to_i # seconds
sleep(timeout) sleep(timeout)
@ -185,6 +187,10 @@ class DockerClient
kill_container(container) kill_container(container)
end end
end end
#ensure
# guarantee that the thread is releasing the DB connection after it is done
# ActiveRecord::Base.connectionpool.releaseconnection
#end
end end
def exit_container(container) def exit_container(container)
@ -233,6 +239,7 @@ class DockerClient
end end
def self.find_image_by_tag(tag) def self.find_image_by_tag(tag)
# todo: cache this.
Docker::Image.all.detect { |image| image.info['RepoTags'].flatten.include?(tag) } Docker::Image.all.detect { |image| image.info['RepoTags'].flatten.include?(tag) }
end end
@ -246,8 +253,10 @@ class DockerClient
def initialize(options = {}) def initialize(options = {})
@execution_environment = options[:execution_environment] @execution_environment = options[:execution_environment]
@image = self.class.find_image_by_tag(@execution_environment.docker_image) # todo: eventually re-enable this if it is cached. But in the end, we do not need this.
fail(Error, "Cannot find image #{@execution_environment.docker_image}!") unless @image # docker daemon got much too much load. all not 100% necessary calls to the daemon were removed.
#@image = self.class.find_image_by_tag(@execution_environment.docker_image)
#fail(Error, "Cannot find image #{@execution_environment.docker_image}!") unless @image
end end
def self.initialize_environment def self.initialize_environment
@ -255,7 +264,9 @@ class DockerClient
fail(Error, 'Docker configuration missing!') fail(Error, 'Docker configuration missing!')
end end
Docker.url = config[:host] if config[:host] Docker.url = config[:host] if config[:host]
check_availability! # todo: availability check disabled for performance reasons. Reconsider if this is necessary.
# docker daemon got much too much load. all not 100% necessary calls to the daemon were removed.
# check_availability!
FileUtils.mkdir_p(LOCAL_WORKSPACE_ROOT) FileUtils.mkdir_p(LOCAL_WORKSPACE_ROOT)
end end
@ -298,7 +309,7 @@ class DockerClient
output = container.exec(['bash', '-c', command]) output = container.exec(['bash', '-c', command])
Rails.logger.info "output from container.exec" Rails.logger.info "output from container.exec"
Rails.logger.info output Rails.logger.info output
result = {status: output[2] == 0 ? :ok : :failed, stdout: output[0].join, stderr: output[1].join} result = {status: output[2] == 0 ? :ok : :failed, stdout: output[0].join.force_encoding('utf-8'), stderr: output[1].join.force_encoding('utf-8')}
end end
# if we use pooling and recylce the containers, put it back. otherwise, destroy it. # if we use pooling and recylce the containers, put it back. otherwise, destroy it.
(DockerContainerPool.config[:active] && RECYCLE_CONTAINERS) ? self.class.return_container(container, @execution_environment) : self.class.destroy_container(container) (DockerContainerPool.config[:active] && RECYCLE_CONTAINERS) ? self.class.return_container(container, @execution_environment) : self.class.destroy_container(container)