Show More
Commit Description:
better all sub progres report
Commit Description:
better all sub progres report
References:
File last commit:
Show/Diff file:
Action:
grader
| 473 lines
| 11.6 KiB
| text/plain
| TextLexer
|
|
r137 | #!/usr/bin/env ruby | ||
|
r0 | |||
|
r24 | def stop_grader(id) | ||
if id==:all | ||||
File.open(File.dirname(__FILE__) + "/stop.all",'w').close | ||||
else | ||||
File.open(File.dirname(__FILE__) + "/stop.#{id}",'w').close | ||||
end | ||||
|
r6 | end | ||
def check_stopfile | ||||
|
r24 | FileTest.exist?(File.dirname(__FILE__) + "/stop.all") or | ||
FileTest.exist?(File.dirname(__FILE__) + "/stop.#{Process.pid}") | ||||
|
r6 | end | ||
def clear_stopfile | ||||
|
r24 | if FileTest.exist?(File.dirname(__FILE__) + "/stop.#{Process.pid}") | ||
|
r99 | File.delete(File.dirname(__FILE__) + "/stop.#{Process.pid}") | ||
|
r24 | end | ||
|
r0 | end | ||
|
r6 | |||
|
r22 | def config | ||
Grader::Configuration.get_instance | ||||
end | ||||
|
r23 | def log_file_name | ||
|
r25 | if !File.exists?(config.log_dir) | ||
raise "Log directory does not exist: #{config.log_dir}" | ||||
end | ||||
|
r23 | config.log_dir + | ||
"/#{GRADER_ENV}_#{config.grader_mode}.#{Process.pid}" | ||||
end | ||||
def log(str) | ||||
|
r22 | if config.talkative | ||
puts str | ||||
end | ||||
|
r23 | if config.logging | ||
fp = File.open(log_file_name,"a") | ||||
fp.puts("GRADER: #{Time.new.strftime("%H:%M")} #{str}") | ||||
fp.close | ||||
end | ||||
end | ||||
def display_manual | ||||
puts <<USAGE | ||||
Grader. | ||||
using: (1) grader | ||||
r163 | (2) grader environment [mode] [options] | |||
|
r24 | (3) grader stop [all|pids-list] | ||
|
r23 | (4) grader --help | ||
(1) call grader with environment = 'exam', mode = 'queue' | ||||
|
r121 | (2) possible modes are: 'queue', 'test_request', 'prob', 'sub', 'contest', and 'autonew' | ||
r163 | queue: repeatedly check the task queue and grade any available tasks | |||
prob: re-grade every user latest submission of the specific problem. | ||||
the problem name must be specified by the next argument. | ||||
additional options: | ||||
r240 | --all-sub re-grade every submissions instead of just the latest submission of each user. | |||
--only-error re-grade only submissions that are "error during grading" | ||||
r163 | ||||
sub: re-grader the specified submission. | ||||
The submission ID to be re-graded must be specified by the next argument. | ||||
r174 | options: | |||
--err-log log error to a file in the log dir | ||||
|
r23 | (3) create stop-file to stop running grader in queue mode | ||
(4) You are here. | ||||
USAGE | ||||
|
r22 | end | ||
|
r91 | def process_options_and_stop_file | ||
# The list of options are: | ||||
# - stop [all|process ids] | ||||
# - | ||||
# Process 'help' option | ||||
if (ARGV.length==1) and (/help/.match(ARGV[0])) | ||||
display_manual | ||||
exit(0) | ||||
end | ||||
# Process 'stop' option. | ||||
if (ARGV.length >= 1) and (ARGV[0]=='stop') | ||||
if ARGV.length==1 | ||||
puts "you should specify pid-list or 'all'" | ||||
display_manual | ||||
elsif (ARGV.length==2) and (ARGV[1]=='all') | ||||
stop_grader(:all) | ||||
puts "A global stop file ('stop.all') created." | ||||
puts "You should remove it manually later." | ||||
else | ||||
(1..ARGV.length-1).each do |i| | ||||
stop_grader(ARGV[i]) | ||||
end | ||||
puts "stop file(s) created" | ||||
end | ||||
exit(0) | ||||
end | ||||
# Check stop file. | ||||
if check_stopfile | ||||
puts "Stop file exists. Terminated." | ||||
clear_stopfile | ||||
exit(0) | ||||
end | ||||
#default options | ||||
options = { | ||||
:mode => 'queue', | ||||
:environment => 'exam', | ||||
:dry_run => false, | ||||
} | ||||
# Process mode and environment option | ||||
if ARGV.length >= 1 | ||||
options[:environment] = ARGV.shift | ||||
if ARGV.length >=1 | ||||
options[:mode] = ARGV.shift | ||||
end | ||||
r163 | else | |||
puts 'no argument specified, using default mode and environment.' | ||||
|
r91 | end | ||
options[:dry_run] = (ARGV.delete('--dry') != nil) | ||||
|
r121 | if options[:dry_run] and (not ['prob','contest','autonew'].include? options[:mode]) | ||
|
r94 | puts "Dry run currently works only for 'prob' or 'contest' modes." | ||
|
r92 | exit(0) | ||
end | ||||
options[:report] = (ARGV.delete('--report') != nil) | ||||
|
r122 | if options[:report] and (not ['prob','contest','autonew'].include? options[:mode]) | ||
|
r94 | puts "Report currently works only for 'prob' or 'contest' modes." | ||
|
r91 | exit(0) | ||
end | ||||
r163 | options[:all_sub] = (ARGV.delete('--all-sub') != nil) | |||
r240 | options[:only_err] = (ARGV.delete('--only-error') != nil) | |||
r163 | ||||
r174 | options[:err_log] = (ARGV.delete('--err-log') != nil) | |||
|
r91 | return options | ||
|
r23 | end | ||
|
r92 | class ResultCollector | ||
def initialize | ||||
@results = {} | ||||
@problems = {} | ||||
@users = {} | ||||
end | ||||
|
r123 | def after_save_hook(submission, grading_result) | ||
|
r122 | end | ||
|
r123 | def save(submission, grading_result) | ||
user = submission.user | ||||
problem = submission.problem | ||||
|
r92 | if not @problems.has_key? problem.id | ||
@problems[problem.id] = problem | ||||
end | ||||
if not @users.has_key? user.id | ||||
@users[user.id] = user | ||||
end | ||||
@results[[user.id, problem.id]] = grading_result | ||||
|
r122 | |||
|
r123 | after_save_hook(submission, grading_result) | ||
|
r92 | end | ||
def print_report_by_user | ||||
puts "---------------------" | ||||
puts " REPORT" | ||||
puts "---------------------" | ||||
|
r94 | print "login,email" | ||
|
r92 | @problems.each_value do |problem| | ||
print ",#{problem.name}" | ||||
end | ||||
print "\n" | ||||
@users.each_value do |user| | ||||
|
r94 | print "#{user.login},#{user.email}" | ||
|
r92 | @problems.each_value do |problem| | ||
if @results.has_key? [user.id, problem.id] | ||||
print ",#{@results[[user.id,problem.id]][:points]}" | ||||
else | ||||
print "," | ||||
end | ||||
end | ||||
print "\n" | ||||
end | ||||
end | ||||
end | ||||
|
r121 | def grader_general_loop(engine, grader_proc, options) | ||
runner = Grader::Runner.new(engine, grader_proc) | ||||
while true | ||||
if check_stopfile # created by calling grader stop | ||||
clear_stopfile | ||||
log "stopped (with stop file)" | ||||
break | ||||
end | ||||
task = yield(runner) | ||||
if task==nil | ||||
sleep(1) | ||||
end | ||||
end | ||||
end | ||||
def grader_queue_loop(grader_proc, options) | ||||
log "Grader: queue" | ||||
engine = Grader::Engine.new | ||||
grader_general_loop(engine, grader_proc, options) do |runner| | ||||
runner.grade_oldest_task | ||||
end | ||||
end | ||||
def grader_test_request_loop(grader_proc, options) | ||||
log "Grader: test_request" | ||||
engine = Grader::Engine.new(:room_maker => Grader::TestRequestRoomMaker.new, | ||||
:reporter => Grader::TestRequestReporter.new) | ||||
grader_general_loop(engine, grader_proc, options) do |runner| | ||||
runner.grade_oldest_test_request | ||||
end | ||||
end | ||||
def grader_autonew_loop(grader_proc, options) | ||||
log "Grader: autonew" | ||||
|
r122 | if options[:report] | ||
result_collector = ResultCollector.new | ||||
else | ||||
result_collector = nil | ||||
end | ||||
|
r121 | if options[:dry_run] | ||
puts "Running in dry mode" | ||||
end | ||||
|
r122 | prob_reporter = Grader::SubmissionReporter.new(:dry_run => options[:dry_run], | ||
:result_collector => result_collector) | ||||
|
r121 | engine = Grader::Engine.new(:reporter => prob_reporter) | ||
runner = Grader::Runner.new(engine, grader_proc) | ||||
grader_proc.report_active if grader_proc!=nil | ||||
latest_submitted_at = nil | ||||
graded_submission_ids = {} | ||||
while true | ||||
if check_stopfile # created by calling grader stop | ||||
clear_stopfile | ||||
log "stopped (with stop file)" | ||||
break | ||||
end | ||||
if latest_submitted_at==nil | ||||
submissions = Submission.all | ||||
else | ||||
submissions = Submission.all(:conditions => ["submitted_at >= :latest", | ||||
{:latest => latest_submitted_at}]) | ||||
end | ||||
graded_any = false | ||||
if submissions.length != 0 | ||||
submissions.each do |submission| | ||||
|
r133 | if (submission.problem == nil) or (!submission.problem.available) | ||
next | ||||
end | ||||
|
r121 | if ! graded_submission_ids[submission.id] | ||
runner.grade_submission(submission) | ||||
graded_submission_ids[submission.id] = true | ||||
if (!latest_submitted_at or | ||||
latest_submitted_at < submission.submitted_at) | ||||
latest_submitted_at = submission.submitted_at | ||||
end | ||||
puts "graded: #{submission.id}" | ||||
puts "latest: #{latest_submitted_at}" | ||||
graded_any = true | ||||
end | ||||
end | ||||
end | ||||
if ! graded_any | ||||
sleep(1) | ||||
end | ||||
end | ||||
end | ||||
def grader_grade_problems(grader_proc, options) | ||||
if options[:report] | ||||
result_collector = ResultCollector.new | ||||
else | ||||
result_collector = nil | ||||
end | ||||
if options[:dry_run] | ||||
puts "Running in dry mode" | ||||
end | ||||
prob_reporter = Grader::SubmissionReporter.new(:dry_run => options[:dry_run], | ||||
:result_collector => result_collector) | ||||
engine = Grader::Engine.new(:reporter => prob_reporter) | ||||
runner = Grader::Runner.new(engine, grader_proc) | ||||
grader_proc.report_active if grader_proc!=nil | ||||
ARGV.each do |prob_name| | ||||
prob = Problem.find_by_name(prob_name) | ||||
if prob==nil | ||||
puts "cannot find problem: #{prob_name}" | ||||
else | ||||
r163 | runner.grade_problem(prob,options) | |||
|
r121 | end | ||
end | ||||
if options[:report] | ||||
result_collector.print_report_by_user | ||||
end | ||||
end | ||||
def grader_grade_contests(grader_proc, options) | ||||
# always use dry run when grading during contest | ||||
dry_run = options[:dry_run] = true | ||||
contest_name = ARGV.shift | ||||
contest = Contest.find_by_name(contest_name) | ||||
if contest==nil | ||||
puts "cannot find contest: #{contest_name}" | ||||
exit(0) | ||||
end | ||||
if options[:report] | ||||
result_collector = ResultCollector.new | ||||
else | ||||
result_collector = nil | ||||
end | ||||
if options[:dry_run] | ||||
puts "Running in dry mode" | ||||
end | ||||
prob_reporter = Grader::SubmissionReporter.new(:dry_run => dry_run, | ||||
:result_collector => result_collector) | ||||
engine = Grader::Engine.new(:reporter => prob_reporter) | ||||
runner = Grader::Runner.new(engine, grader_proc) | ||||
grader_proc.report_active if grader_proc!=nil | ||||
contest.problems.each do |problem| | ||||
puts "Grading: #{problem.name}" | ||||
runner.grade_problem(problem, | ||||
:user_conditions => lambda do |u| | ||||
u.contest_finished? and | ||||
u.contest_ids.include?(contest.id) | ||||
end) | ||||
end | ||||
if options[:report] | ||||
result_collector.print_report_by_user | ||||
end | ||||
end | ||||
def grader_grade_submissions(grader_proc, options) | ||||
engine = Grader::Engine.new | ||||
runner = Grader::Runner.new(engine, grader_proc) | ||||
grader_proc.report_active if grader_proc!=nil | ||||
ARGV.each do |sub_id| | ||||
puts "Grading #{sub_id}" | ||||
begin | ||||
submission = Submission.find(sub_id.to_i) | ||||
rescue ActiveRecord::RecordNotFound | ||||
r150 | puts "Submission #{sub_id} not found" | |||
|
r121 | submission = nil | ||
end | ||||
if submission!=nil | ||||
runner.grade_submission(submission) | ||||
end | ||||
end | ||||
end | ||||
|
r92 | ######################################### | ||
# main program | ||||
######################################### | ||||
|
r91 | options = process_options_and_stop_file | ||
GRADER_ENV = options[:environment] | ||||
grader_mode = options[:mode] | ||||
dry_run = options[:dry_run] | ||||
|
r6 | |||
puts "environment: #{GRADER_ENV}" | ||||
r163 | puts "grader mode: #{grader_mode}" | |||
|
r22 | require File.join(File.dirname(__FILE__),'config/environment') | ||
|
r0 | |||
|
r23 | # add grader_mode to config | ||
# this is needed because method log needs it. TODO: clean this up | ||||
class << config | ||||
attr_accessor :grader_mode | ||||
end | ||||
config.grader_mode = grader_mode | ||||
# reading rails environment | ||||
log 'Reading rails environment' | ||||
|
r0 | |||
|
r22 | RAILS_ENV = config.rails_env | ||
require RAILS_ROOT + '/config/environment' | ||||
|
r23 | # register grader process | ||
|
r22 | if config.report_grader | ||
grader_proc = GraderProcess.register(config.grader_hostname, | ||||
|
r20 | Process.pid, | ||
grader_mode) | ||||
else | ||||
grader_proc = nil | ||||
end | ||||
|
r23 | #set loggin environment | ||
ENV['GRADER_LOGGING'] = log_file_name | ||||
r174 | if options[:err_log] | |||
err_file_name = log_file_name + '.err' | ||||
$stderr.reopen(err_file_name,"a") | ||||
log "STDERR log to file [#{err_file_name}]" | ||||
r176 | warn "start logging for grader PID #{Process.pid} on #{Time.now.in_time_zone}" | |||
r174 | end | |||
|
r23 | |||
|
r70 | # register exit handler to report inactive, and terminated | ||
at_exit do | ||||
if grader_proc!=nil | ||||
grader_proc.report_inactive | ||||
grader_proc.terminate | ||||
end | ||||
end | ||||
|
r23 | # | ||
# MAIN LOOP | ||||
# | ||||
|
r22 | |||
|
r9 | case grader_mode | ||
|
r121 | when "queue" | ||
grader_queue_loop(grader_proc, options) | ||||
|
r23 | |||
|
r121 | when "test_request" | ||
grader_test_request_loop(grader_proc, options) | ||||
|
r9 | when "prob" | ||
|
r121 | grader_grade_problems(grader_proc, options) | ||
|
r92 | |||
|
r93 | when "contest" | ||
|
r121 | grader_grade_contests(grader_proc, options) | ||
|
r93 | |||
|
r61 | when "sub" | ||
|
r121 | grader_grade_submissions(grader_proc, options) | ||
|
r61 | |||
|
r121 | when "autonew" | ||
grader_autonew_loop(grader_proc, options) | ||||
|
r93 | |||
|
r23 | else | ||
display_manual | ||||
exit(0) | ||||
|
r0 | end | ||