# HG changeset patch # User Nattee Niparnan # Date 2014-09-28 04:44:55 # Node ID 41d1cd2793ff2e2bc0def9d83f2848a833bcd65d # Parent 3968658b7a1320fa662ecd145aac311f74d7cf6c # Parent 9f220a082fcd6723939939095c403c501cddacd5 merge diff --git a/app/controllers/problems_controller.rb b/app/controllers/problems_controller.rb --- a/app/controllers/problems_controller.rb +++ b/app/controllers/problems_controller.rb @@ -150,11 +150,25 @@ def stat @problem = Problem.find(params[:id]) - if !@problem.available + unless @problem.available or session[:admin] redirect_to :controller => 'main', :action => 'list' - else - @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id) + return end + @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id) + + #stat summary + range =65 + @histogram = { data: Array.new(range,0), summary: {} } + user = Hash.new(0) + @submissions.find_each do |sub| + d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60 + @histogram[:data][d.to_i] += 1 if d < range + user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max + end + @histogram[:summary][:max] = [@histogram[:data].max,1].max + + @summary = { attempt: user.count, solve: 0 } + user.each_value { |v| @summary[:solve] += 1 if v == 1 } end def manage @@ -257,4 +271,7 @@ problems end + def get_problems_stat + end + end diff --git a/app/controllers/report_controller.rb b/app/controllers/report_controller.rb --- a/app/controllers/report_controller.rb +++ b/app/controllers/report_controller.rb @@ -105,91 +105,87 @@ end end - if @problem - #aggregrate by language - @by_lang = {} - Submission.where(problem_id: @problem.id).find_each do |sub| - lang = Language.find_by_id(sub.language_id) - next unless lang - next unless sub.points >= @problem.full_score + return unless @problem + + @by_lang = {} #aggregrate by language - #initialize - unless @by_lang.has_key?(lang.pretty_name) - @by_lang[lang.pretty_name] = { - runtime: { avail: false, value: 2**30-1 }, - memory: { avail: false, value: 2**30-1 }, - length: { avail: false, value: 2**30-1 }, - first: { avail: false, value: DateTime.new(3000,1,1) } - } - end + range =65 + @histogram = { data: Array.new(range,0), summary: {} } + @summary = {count: 0, solve: 0, attempt: 0} + user = Hash.new(0) + Submission.where(problem_id: @problem.id).find_each do |sub| + #histogram + d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60 + @histogram[:data][d.to_i] += 1 if d < range - if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value] - @by_lang[lang.pretty_name][:runtime] = { - avail: true, - user_id: sub.user_id, - value: sub.max_runtime, - sub_id: sub.id - } - end + @summary[:count] += 1 + user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max - if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value] - @by_lang[lang.pretty_name][:memory] = { - avail: true, - user_id: sub.user_id, - value: sub.peak_memory, - sub_id: sub.id - } - end + lang = Language.find_by_id(sub.language_id) + next unless lang + next unless sub.points >= @problem.full_score - if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and - !sub.user.admin? - @by_lang[lang.pretty_name][:first] = { - avail: true, - user_id: sub.user_id, - value: sub.submitted_at, - sub_id: sub.id - } - end + #initialize + unless @by_lang.has_key?(lang.pretty_name) + @by_lang[lang.pretty_name] = { + runtime: { avail: false, value: 2**30-1 }, + memory: { avail: false, value: 2**30-1 }, + length: { avail: false, value: 2**30-1 }, + first: { avail: false, value: DateTime.new(3000,1,1) } + } + end - if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length - @by_lang[lang.pretty_name][:length] = { - avail: true, - user_id: sub.user_id, - value: sub.effective_code_length, - sub_id: sub.id - } - end - end - - #process user_id - @by_lang.each do |lang,prop| - prop.each do |k,v| - v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)" - end + if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value] + @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id } end - #sum into best - if @by_lang and @by_lang.first - @best = @by_lang.first[1].clone - @by_lang.each do |lang,prop| - if @best[:runtime][:value] >= prop[:runtime][:value] - @best[:runtime] = prop[:runtime] - @best[:runtime][:lang] = lang - end - if @best[:memory][:value] >= prop[:memory][:value] - @best[:memory] = prop[:memory] - @best[:memory][:lang] = lang - end - if @best[:length][:value] >= prop[:length][:value] - @best[:length] = prop[:length] - @best[:length][:lang] = lang - end - if @best[:first][:value] >= prop[:first][:value] - @best[:first] = prop[:first] - @best[:first][:lang] = lang - end + if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value] + @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id } + end + + if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and + !sub.user.admin? + @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id } + end + + if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length + @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id } + end + end + + #process user_id + @by_lang.each do |lang,prop| + prop.each do |k,v| + v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)" + end + end + + #sum into best + if @by_lang and @by_lang.first + @best = @by_lang.first[1].clone + @by_lang.each do |lang,prop| + if @best[:runtime][:value] >= prop[:runtime][:value] + @best[:runtime] = prop[:runtime] + @best[:runtime][:lang] = lang + end + if @best[:memory][:value] >= prop[:memory][:value] + @best[:memory] = prop[:memory] + @best[:memory][:lang] = lang + end + if @best[:length][:value] >= prop[:length][:value] + @best[:length] = prop[:length] + @best[:length][:lang] = lang + end + if @best[:first][:value] >= prop[:first][:value] + @best[:first] = prop[:first] + @best[:first][:lang] = lang end end end + + @histogram[:summary][:max] = [@histogram[:data].max,1].max + @summary[:attempt] = user.count + user.each_value { |v| @summary[:solve] += 1 if v == 1 } end + end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -111,7 +111,25 @@ def profile @user = User.find(params[:id]) - @submission = Submission.where(user_id: params[:id]).all + @submission = Submission.includes(:problem).where(user_id: params[:id]) + + range = 120 + @histogram = { data: Array.new(range,0), summary: {} } + @summary = {count: 0, solve: 0, attempt: 0} + problem = Hash.new(0) + + @submission.find_each do |sub| + #histogram + d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60 + @histogram[:data][d.to_i] += 1 if d < range + + @summary[:count] += 1 + problem[sub.problem] = [problem[sub.problem], (sub.points >= sub.problem.full_score) ? 1 : 0].max + end + + @histogram[:summary][:max] = [@histogram[:data].max,1].max + @summary[:attempt] = problem.count + problem.each_value { |v| @summary[:solve] += 1 if v == 1 } end protected diff --git a/app/models/problem.rb b/app/models/problem.rb --- a/app/models/problem.rb +++ b/app/models/problem.rb @@ -54,6 +54,13 @@ def self.download_file_basedir return "#{Rails.root}/data/tasks" end + + def get_submission_stat + result = Hash.new + #total number of submission + result[:total_sub] = Submission.where(problem_id: self.id).count + result[:attempted_user] = Submission.where(problem_id: self.id).group_by(:user_id) + end protected diff --git a/app/views/application/_bar_graph.html.haml b/app/views/application/_bar_graph.html.haml new file mode 100644 --- /dev/null +++ b/app/views/application/_bar_graph.html.haml @@ -0,0 +1,44 @@ +- param = {} unless param +- graph_height = param[:graph_height] || 100 +- bar_width = param[:bar_width] || 14 +- graph_width = (bar_width * histogram[:data].count) + 20 +:css + .hist_bar { + width: #{bar_width-1}px; + position: absolute; + background-color: lightblue; + } + .hist_fill { + width: #{bar_width-1}px; + position: absolute; + background-color: #eee; + } + .hist_text { + position: absolute; + font-size:5px; + } + +%div{style: "position: relative; width: #{graph_width}px; height: 125px; background-color:#fff;" } + //draw background + - histogram[:data].each_index do |i| + - height = histogram[:data][i] * graph_height / histogram[:summary][:max] + - top = graph_height - height + - left = graph_width - (i+1)*bar_width + %div.hist_fill{style: "top: 0px; height: #{graph_height - height}px; left: #{left}px;" } + // draw horizontal line + - line = 3 + - line.times do |i| + - top = graph_height - graph_height * (i+0.5)/ line + %div{style: "position:absolute;width: #{graph_width-21}px;height: 1px;left: 20px;top:#{top}px;background-color: #333;"} + %div.hist_text{style: "position:absolute;left: 0px;top:#{top-6}px"} + =((i+0.5) * histogram[:summary][:max] / line).to_i + // draw the actual bar and text + - @histogram[:data].each_index do |i| + - height = histogram[:data][i] * graph_height / histogram[:summary][:max] + - top = graph_height - height + - left = graph_width - (i+1)*bar_width + %div.hist_bar{style: "top: #{top}px; height: #{height}px; left: #{left}px; dae: #{histogram[:data][i]}" } + - if i % 7 == 1 + %div.hist_text{style: "top:#{graph_height + 5}px;left: #{left}px;"} #{(Time.zone.today - i.day).strftime('%-d')} + - if (Time.now.in_time_zone - i.day).day == 15 + %div.hist_text{style: "top:#{graph_height + 15}px;left: #{left}px;"} #{(Time.zone.today - i.day).strftime('%b')} diff --git a/app/views/graders/submission.html.haml b/app/views/graders/submission.html.haml --- a/app/views/graders/submission.html.haml +++ b/app/views/graders/submission.html.haml @@ -21,12 +21,18 @@ %tbody %tr{class: cycle('info-even','info-odd')} %td.field User: - %td.value= "(#{@submission.user.login}) #{@submission.user.full_name}" + %td.value + - if @submission.user + = link_to "(#{@submission.user.login})", controller: "users", action: "profile", id: @submission.user + = @submission.user.full_name + - else + = "(n/a)" %tr{class: cycle('info-even','info-odd')} %td.field Problem: %td.value - if @submission.problem!=nil - = "(#{@submission.problem.name}) #{@submission.problem.full_name}" + = link_to "(#{@submission.problem.name})", controller: "problems", action: "stat", id: @submission.problem + = @submission.problem.full_name - else = "(n/a)" %tr{class: cycle('info-even','info-odd')} diff --git a/app/views/problems/stat.html.haml b/app/views/problems/stat.html.haml --- a/app/views/problems/stat.html.haml +++ b/app/views/problems/stat.html.haml @@ -6,6 +6,23 @@ %h1 Problem stat: #{@problem.name} %h2 Overview + +%table.info + %thead + %tr.info-head + %th Stat + %th Value + %tbody + %tr{class: cycle('info-even','info-odd')} + %td Submissions + %td= @submissions.count + %tr{class: cycle('info-even','info-odd')} + %td Solved/Attempted User + %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%) + +%h2 Submissions Count += render partial: 'application/bar_graph', locals: { histogram: @histogram } + %h2 Submissions - if @submissions and @submissions.count > 0 %table.info#main_table diff --git a/app/views/report/_task_hof.html.haml b/app/views/report/_task_hof.html.haml --- a/app/views/report/_task_hof.html.haml +++ b/app/views/report/_task_hof.html.haml @@ -1,48 +1,99 @@ +- content_for :header do + = javascript_include_tag 'local_jquery' + +:javascript + $(document).ready( function() { + $("#mem_remark").hover( function() { + $("#mem_remark_box").show(); + }, function() { + $("#mem_remark_box").hide(); + }); + }); + alert("hahaha"); :css .hof_user { color: orangered; font-style: italic; } .hof_language { color: green; font-style: italic; } .hof_value { color: deeppink;font-style: italic; } + .info_param { font-weight: bold;text-align: right; } + .tooltip { + font-family: Verdana,sans-serif; + font-weight: normal; + text-align: left; + font-size: 1.0em; + color: black; + line-height: 1.1; + display: none; + min-width: 20em; + position: absolute; + left: 25px; + bottom: 5px; + border: 1px solid; + padding: 5px; + background-color: #FFF; + word-wrap: break-word; + z-index: 9999; + overflow: auto; + } -%h2 Overall of #{Problem.find(params[:id]).full_name} +%h1 (#{Problem.find(params[:id]).name}) #{Problem.find(params[:id]).full_name} + +%h2 Problem Stat +%table.info + %thead + %tr.info-head + %th Stat + %th Value + %tbody + %tr{class: cycle('info-even','info-odd')} + %td.info_param Submissions + %td= @summary[:count] + %tr{class: cycle('info-even','info-odd')} + %td.info_param Solved/Attempted User + %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%) + - if @best + %tr{class: cycle('info-even','info-odd')} + %td.info_param Best Runtime + %td + by #{link_to @best[:runtime][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]} + using #{@best[:runtime][:lang]} + with #{@best[:runtime][:value] * 1000} milliseconds + at submission + = link_to("#" + @best[:runtime][:sub_id].to_s, controller: 'graders', action: 'submission', id:@best[:runtime][:sub_id]) + + %tr{class: cycle('info-even','info-odd')} + %td.info_param + Best Memory Usage + %sup{ id: "mem_remark", style: "position:relative; color: blue;"} + [?] + %span.tooltip#mem_remark_box + This counts only for submission with 100% score. + Right now, java is excluded from memory usage competition. (Because it always uses 2GB memory...) + %td + by #{link_to @best[:memory][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]} + using #{@best[:memory][:lang]} + with #{number_with_delimiter(@best[:memory][:value])} kbytes + at submission + = link_to("#" + @best[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id:@best[:memory][:sub_id]) + + %tr{class: cycle('info-even','info-odd')} + %td.info_param Shortest Code + %td + by #{link_to @best[:length][:user], controller:'users', action:'profile', id:@best[:length][:user_id]} + using #{@best[:length][:lang]} + with #{@best[:length][:value]} bytes + at submission + = link_to("#" + @best[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:length][:sub_id]) + + %tr{class: cycle('info-even','info-odd')} + %td.info_param First solver + %td + #{link_to @best[:first][:user], controller:'users', action:'profile', id:@best[:first][:user_id]} is the first solver + using #{@best[:first][:lang]} + on #{@best[:first][:value]} + at submission + = link_to("#" + @best[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:first][:sub_id]) - if @best - %b Best Runtime: - by #{link_to @best[:runtime][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]} - using #{@best[:runtime][:lang]} - with #{@best[:runtime][:value] * 1000} milliseconds - at submission - = link_to("#" + @best[:runtime][:sub_id].to_s, controller: 'graders', action: 'submission', id:@best[:runtime][:sub_id]) - %br/ - - %b Best Memory Usage: - by #{link_to @best[:memory][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]} - using #{@best[:memory][:lang]} - with #{number_with_delimiter(@best[:memory][:value])} kbytes - at submission - = link_to("#" + @best[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id:@best[:memory][:sub_id]) - %br/ - - %b Shortest Code: - by #{link_to @best[:length][:user], controller:'users', action:'profile', id:@best[:length][:user_id]} - using #{@best[:length][:lang]} - with #{@best[:length][:value]} bytes - at submission - = link_to("#" + @best[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:length][:sub_id]) - %br/ - - %b First solver: - #{link_to @best[:first][:user], controller:'users', action:'profile', id:@best[:first][:user_id]} is the first solver - using #{@best[:first][:lang]} - on #{@best[:first][:value]} - at submission - = link_to("#" + @best[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:first][:sub_id]) - %br/ - - - %p - This counts only for submission with 100% score
- Right now, java is excluded from memory usage competition. (Because it always uses 2GB memory...) - %h2 By language %table.info @@ -75,5 +126,3 @@ = "(#{value[:first][:value]} @" = "#{link_to("#" + value[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:first][:sub_id])} )".html_safe -- else - %h3 No submissions diff --git a/app/views/report/problem_hof.html.haml b/app/views/report/problem_hof.html.haml --- a/app/views/report/problem_hof.html.haml +++ b/app/views/report/problem_hof.html.haml @@ -6,7 +6,7 @@ / %h1 All-Time Hall of Fame -%h1 Tasks Hall of Fame +%h1 Hall of Fame .task-menu Tasks %br/ @@ -18,5 +18,6 @@ Please select a problem. - else =render partial: 'task_hof' + %h2 Submission History + =render partial: 'application/bar_graph', locals: { histogram: @histogram } - diff --git a/app/views/users/profile.html.haml b/app/views/users/profile.html.haml --- a/app/views/users/profile.html.haml +++ b/app/views/users/profile.html.haml @@ -1,9 +1,9 @@ - content_for :header do = javascript_include_tag 'local_jquery' -%script{:type=>"text/javascript"} +:javascript $(function () { - $('#submission_table').tablesorter({widgets: ['zebra']}); + $('#submission_table').tablesorter({widgets: ['zebra']}); }); :css @@ -11,16 +11,30 @@ font-family: Droid Sans Mono,Consolas, monospace, mono, Courier New, Courier; } -%h1= @user.full_name + ' Profile' +%h1= @user.full_name -%h2 Basic info Login: #{@user.login}
Full name: #{@user.full_name}
%h2 Problem Stat +%table.info + %thead + %tr.info-head + %th Stat + %th Value + %tbody + %tr{class: cycle('info-even','info-odd')} + %td.info_param Submissions + %td= @summary[:count] + %tr{class: cycle('info-even','info-odd')} + %td.info_param Solved/Attempted Problem + %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%) -%h2 Submissions +%h2 Submission History + +=render partial: 'application/bar_graph', locals: {histogram: @histogram, param: {bar_width: 7}} + %table.tablesorter-cafe#submission_table %thead @@ -39,7 +53,7 @@ - next unless s.problem %tr %td= link_to "#{s.id}", controller: "graders", action: "submission", id: s.id - %td= s.problem.name + %td= link_to s.problem.name, controller: "problems", action: "stat", id: s.problem %td= s.problem.full_name %td= s.language.pretty_name %td #{s.submitted_at.strftime('%Y-%m-%d %H:%M')} (#{time_ago_in_words(s.submitted_at)} ago) diff --git a/lib/grader_script.rb b/lib/grader_script.rb --- a/lib/grader_script.rb +++ b/lib/grader_script.rb @@ -29,8 +29,8 @@ end def self.start_grader(env) - GraderScript.call_grader "#{env} queue &" - GraderScript.call_grader "#{env} test_request &" + GraderScript.call_grader "#{env} queue --err-log &" + GraderScript.call_grader "#{env} test_request -err-log &" end def self.call_import_problem(problem_name,