Index: common/common.php
===================================================================
--- common/common.php	(revision 1151)
+++ common/common.php	(working copy)
@@ -86,6 +86,17 @@ define("IA_USER_DEFINED_ROUND_DAYSBEFORE_LIMIT", '30');
 // Users can't create user defined rounds with too many problems
 define("IA_USER_DEFINED_ROUND_TASK_LIMIT", '25');
 
+// Constants for judge run messages
+// Extracted from jrun/jrun.c.
+// FIXME: These constants should be defined as constants in jrun.h as well
+define('IA_JRUN_OK',      'Execution successful.');
+define('IA_JRUN_TLE',     'Time limit exceeded');
+define('IA_JRUN_MLE',     'Memory limit exceeded.');
+define('IA_JRUN_WTLE',    'Wall time limit exceeded.');
+define('IA_JRUN_BLOCKED', 'Blocked system call: %s.');
+define('IA_JRUN_NONZERO', 'Non-zero exit status.');
+define('IA_JRUN_KBS',     'Killed by signal %d(%s).');
+
 // Windows hack for checkdnsrr function.
 // FIXME: Not fully tested!
 if (!function_exists('checkdnsrr')) {
Index: common/db/job.php
===================================================================
--- common/db/job.php	(revision 1151)
+++ common/db/job.php	(working copy)
@@ -221,7 +221,7 @@ function job_get_range_wheres($filters) {
 }
 
 // Get a range of jobs, ordered by submit time. Really awesome filterss!
-function job_get_range($filters, $start, $range) {
+function job_get_range($filters, $start=0, $range=0, $fetch_contents=false) {
     log_assert(is_whole_number($start));
     log_assert(is_whole_number($range));
     log_assert($start >= 0);
@@ -243,7 +243,8 @@ function job_get_range($filters, $start, $range) {
             `job`.`eval_message`,
             `job`.`eval_log`,
             `job`.`remote_ip_info`,
-            OCTET_LENGTH(`job`.`file_contents`) AS `job_size`,
+            OCTET_LENGTH(`job`.`file_contents`) AS `job_size`," .
+            ($fetch_contents ? "`job`.`file_contents`," : "") . "
             `user`.`username` AS `user_name`,
             `user`.`full_name` AS `user_fullname`,
             `task`.`page_name` AS `task_page_name`,
@@ -271,13 +272,17 @@ function job_get_range($filters, $start, $range) {
         LEFT JOIN `ia_round` AS `round` ON `job`.`round_id` = `round`.`id`
         LEFT JOIN `ia_user` AS `user` ON `job`.`user_id` = `user`.`id`";
 
+    $limit = "";
+    if ($start != 0 || $range != 0) {
+        $limit = "LIMIT {$start}, ${range}";
+    }
     if (!isset($wheres[1])) {
         // if we have no filters outside of `ia_job` table then optimize query
         $subquery = "
             SELECT `job`.`id` AS `ID`
             FROM `ia_job` as `job`
             WHERE (" . implode(") AND (", $wheres_job) . ")
-            ORDER BY `id` DESC LIMIT {$start}, {$range}";
+            ORDER BY `id` DESC {$limit}";
 
         $job_ids_fetched = db_fetch_all($subquery);
         foreach ($job_ids_fetched as $job_id) {
@@ -293,7 +298,7 @@ function job_get_range($filters, $start, $range) {
         // we have filters outside of `ia_job` table, we can't query in query
         $query .= "
             WHERE (".implode(") AND (", $wheres).") AND (".implode(") AND (", $wheres_job).")";
-        $query .= sprintf(" ORDER BY `job`.`id` DESC LIMIT %s, %s", $start, $range);
+        $query .= " ORDER BY `job`.`id` DESC {$limit}";
     }
 
     $result = db_fetch_all($query);
Index: eval/classic_grader.php
===================================================================
--- eval/classic_grader.php	(revision 1151)
+++ eval/classic_grader.php	(working copy)
@@ -1,6 +1,8 @@
 <?php
 
 require_once(IA_ROOT_DIR."common/task.php");
+require_once(IA_ROOT_DIR."eval/utilities.php");
+require_once(IA_ROOT_DIR."eval/download.php");
 
 // Grades a classic task.
 function classic_task_grade_job($task, $tparams, $job) {
Index: scripts/recompute-time-limits
new file mode 100755
===================================================================
--- scripts/recompute-time-limits	(revision 0)
+++ scripts/recompute-time-limits	(working copy)
@@ -0,0 +1,278 @@
+#!/usr/bin/env php
+<?php
+require_once(dirname($argv[0]) . '/utilities.php');
+require_once(IA_ROOT_DIR . 'common/task.php');
+require_once(IA_ROOT_DIR . 'common/job.php');
+require_once(IA_ROOT_DIR . 'eval/config.php');
+require_once(IA_ROOT_DIR . 'eval/classic_grader.php');
+
+db_connect();
+
+// For each task, split jobs into buckets depending on the amount of TLEs they
+// receive. In each bucket choose a number of jobs that are most representative
+// and process them.
+define('BUCKETS', 5);
+define('JOBS_PER_TASK', 25);
+define('BASE_TIME_LIMIT_CONSTANT', 2);
+define('TIME_EPS', 50);
+
+define('COMPUTED_TASKS_FILE', 'tasks');
+define('JUDGE_COMPLETE_MSG', 'Evaluare completa');
+
+/**
+ * Comparison function used to sort jobs inside a bucket by relevance
+ * Sorts descending by score and ascending by total execution time
+ *
+ * @param  array  $a
+ * @param  array  $b
+ */
+function cmp_bucket(&$a, &$b) {
+    if ($a['score'] > $b['score']) {
+        return -1;
+    }
+    if ($a['score'] < $b['score']) {
+        return 1;
+    }
+
+    if ($a['exec_time'] < $b['exec_time']) {
+        return -1;
+    }
+    if ($a['exec_time'] > $b['exec_time']) {
+        return 1;
+    }
+    return 0;
+}
+
+/**
+ * Returns whether or not a test will likely TLE if run with the specified time
+ * limit.
+ *
+ * @param  array  $test
+ * @param  int    $time_limit
+ * @param  int    $eps
+ * @return bool
+ */
+function will_likely_tle($test, $time_limit, $eps = TIME_EPS) {
+    if ($test['grader_message'] == IA_JRUN_TLE) {
+        return true;
+    }
+
+    if ($test['exec_time'] + TIME_EPS / 2 >= $time_limit) {
+        return true;
+    }
+    return false;
+}
+
+/**
+ * Evaluates how adequate a task's new time_limit is.
+ * A subset of tasks have been rejudged with a very high time limit. This
+ * function computes how many TLEs each job would have gotten with the
+ * specified time limit.
+ *
+ * The goal when setting a new limit is to have the number of TLEs for each
+ * job be as close as possible to the ones before and the ratio between total
+ * execution time for each job and total time limit be as close as possible.
+ *
+ * @param  int    $time_limit
+ * @param  array  $jobs
+ * @param  int    $original_time_limit
+ */
+function get_time_rating($time_limit, &$jobs, $original_time_limit) {
+    $tle_rating = $exec_time_rating = 0;
+    foreach ($jobs as $job) {
+        // Skip jobs that couldn't be rejudged
+        if (array_key_exists('skip_me', $job)) {
+            continue;
+        }
+
+        $tle_count = 0;
+        $exec_time = 0;
+        foreach ($job['test_results'] as $test) {
+            if (will_likely_tle($test, $time_limit, TIME_EPS)) {
+                $tle_count += 1;
+                $exec_time += $time_limit + TIME_EPS;
+            } else {
+                $exec_time += $test['exec_time'];
+            }
+        }
+
+        $tle_rating += abs($tle_count - $job['tle_count']);
+        $exec_time_rating += abs(
+            ($exec_time / ($time_limit * $job['test_count'])) -
+            ($job['exec_time'] / ($original_time_limit * $job['test_count'])));
+    }
+    return array($tle_rating, $exec_time_rating);
+}
+
+/**
+ * Gets the list of tasks that have already been processed
+ *
+ * @return array
+ */
+function get_computed_tasks() {
+    if (!file_exists(COMPUTED_TASKS_FILE)) {
+        return array();
+    }
+
+    $file = fopen(COMPUTED_TASKS_FILE, 'r');
+    $ret = array();
+    while (($line = fgets($file)) !== false) {
+        $ret[] = trim($line);
+    }
+
+    return $ret;
+}
+
+/**
+ * Marks a task as processed
+ *
+ * @param  string  $task_id
+ */
+function mark_computed_task($task_id) {
+    $file = fopen(COMPUTED_TASKS_FILE, 'a');
+    fwrite($file, $task_id . "\n");
+    fclose($file);
+}
+
+$computed_tasks = get_computed_tasks();
+
+foreach (task_get_all() as $task) {
+    if (in_array($task['id'], $computed_tasks)) {
+        log_print('Skipping task ' . $task['id']);
+        continue;
+    }
+    log_print('Recomputing times for task ' . $task['id']);
+    $jobs = job_get_range(
+        array('task' => $task['id'],
+              'eval_msg' => JUDGE_COMPLETE_MSG), 0, 0, true);
+    $job_by_id = array();
+    foreach ($jobs as &$job) {
+        if ($job['round_id'] != 'arhiva' &&
+            $job['round_id'] != 'arhiva-educationala') {
+            continue;
+        }
+
+        $job_by_id[$job['id']] =& $job;
+        $job['tle_count'] = 0;
+    }
+
+    if (empty($job_by_id)) {
+        continue;
+    }
+    $escaped_job_ids = implode(', ',
+                               array_map('db_quote', array_keys($job_by_id)));
+
+    // Fetch score, execution time and number of TLEs for jobs
+    // while ignoring test grouping.
+    $scores = db_fetch_all(
+        sprintf('SELECT job_id, SUM(points) AS score, ' .
+                'SUM(exec_time) AS exec_time, COUNT(*) AS test_count ' .
+                ' FROM ia_job_test WHERE job_id IN (%s) GROUP BY job_id',
+                $escaped_job_ids));
+    $tle_count = db_fetch_all(
+        sprintf('SELECT job_id, COUNT(points) AS tle_count FROM ia_job_test ' .
+                'WHERE job_id IN (%s) AND grader_message = ' .
+                '"%s" GROUP BY job_id',
+                $escaped_job_ids, IA_JRUN_TLE));
+
+    foreach ($scores as $row) {
+        $job_by_id[$row['job_id']]['score'] = $row['score'];
+        $job_by_id[$row['job_id']]['exec_time'] = $row['exec_time'];
+        $job_by_id[$row['job_id']]['test_count'] = $row['test_count'];
+    }
+    foreach ($tle_count as $row) {
+        $job_by_id[$row['job_id']]['tle_count'] = $row['tle_count'];
+    }
+
+    // Split jobs into buckets
+    $buckets = array();
+    foreach ($job_by_id as &$job) {
+        if (!isset($job['test_count'])) {
+            log_print('Warning !! Corrupt database for task ' . $task['id'] .
+                ' job ' . $job['id']);
+            continue;
+        }
+        $bucket = (int)(
+            $job['tle_count'] * BUCKETS / ($job['test_count'] + 1));
+        $buckets[$bucket][] =& $job;
+    }
+
+    // Generate list of representative jobs
+    $jobs_for_eval = array();
+    for ($i = 0; $i < BUCKETS; $i++) {
+        if (!array_key_exists($i, $buckets)) {
+            $buckets[$i] = array();
+        }
+        usort($buckets[$i], 'cmp_bucket');
+
+        // Don't fetch more than one job from a certain user in the same bucket
+        $used_users = array();
+
+        $count = ceil(JOBS_PER_TASK / BUCKETS);
+        foreach ($buckets[$i] as &$job) {
+            if (array_key_exists($job['user_id'], $used_users)) {
+                continue;
+            }
+            $jobs_for_eval[] =& $job;
+            $used_users[$job['user_id']] = true;
+            $count -= 1;
+            if ($count == 0) {
+                break;
+            }
+        }
+    }
+
+    log_print(sprintf('Judging %d/%d jobs for task %s.',
+                      count($jobs_for_eval), count($job_by_id), $task['id']));
+    $task_parameters = task_get_parameters($task['id']);
+
+    $original_time_limit = $task_parameters['timelimit'];
+    $task_parameters['timelimit'] = min(
+        60, $task_parameters['timelimit'] * BASE_TIME_LIMIT_CONSTANT);
+    task_update_parameters($task['id'], $task_parameters);
+
+    foreach ($jobs_for_eval as &$job) {
+        log_print(sprintf('Processing job: %6s %22s - %3s - %s - %s',
+                          $job['id'], $job['user_fullname'], $job['score'],
+                          $job['tle_count'], $job['exec_time']));
+
+        $job['original_test_results'] = job_test_get_all($job['id']);
+        // Rejudge the job on the new eval.
+        $grader_result = classic_task_grade_job($task, $task_parameters, $job);
+        if ($grader_result['message'] != JUDGE_COMPLETE_MSG) {
+            $job['skip_me'] = true;
+        }
+        $job['test_results'] = job_test_get_all($job['id']);
+
+        // Put the original scores back in the database
+        foreach ($job['original_test_results'] as $test) {
+            job_test_update($test['job_id'], $test['test_number'],
+                            $test['test_group'], $test['exec_time'],
+                            $test['mem_used'], $test['grader_exec_time'],
+                            $test['grader_mem_used'], $test['points'],
+                            $test['grader_message']);
+        }
+    }
+
+    $best_time = TIME_EPS;
+    $best_rating = get_time_rating(TIME_EPS, $jobs_for_eval,
+                                   $original_time_limit * 1000);
+    for ($i = TIME_EPS * 2; $i <= $task_parameters['timelimit'] * 1000;
+         $i += TIME_EPS) {
+        $rating = get_time_rating($i, $jobs_for_eval,
+                                  $original_time_limit * 1000);
+        if ($rating < $best_rating) {
+            $best_time = $i;
+            $best_rating = $rating;
+        }
+    }
+
+    log_print('The best timelimit for task ' . $task['id'] .
+              ' I could find is ' . $best_time * 0.001 .
+              ' with ' . $best_rating[0] . ' different TLEs');
+    $task_parameters['timelimit'] = $best_time * 0.001;
+    task_update_parameters($task['id'], $task_parameters);
+    mark_computed_task($task['id']);
+}
+
+?>
Index: www/views/job_detail.php
===================================================================
--- www/views/job_detail.php	(revision 1151)
+++ www/views/job_detail.php	(working copy)
@@ -60,16 +60,16 @@ $show_groups = $view['group_tests'] &&
 <?php } ?>
 
 <?php if ('done' == $job['status'] && count($tests) > 0) { ?>
-    <table class="job-eval-tests"> 
+    <table class="job-eval-tests">
 <thead>
     <tr>
         <th>Test</th>
         <th>Timp executie</th>
         <th>Memorie folosita</th>
-        <th>Mesaj</th> 
-        <th>Punctaj/test</th> 
+        <th>Mesaj</th>
+        <th>Punctaj/test</th>
         <?php if ($show_groups) { ?>
-            <th>Punctaj/grupa</th> 
+            <th>Punctaj/grupa</th>
         <?php } ?>
     </tr>
 </thead>
@@ -87,12 +87,13 @@ $show_groups = $view['group_tests'] &&
             $test_row++;
         }
         echo '<td class="number">'.$test['test_number'].'</td>';
-        if ($test["grader_message"] == "Time limit exceeded.") {
+        if ($test['grader_message'] == IA_JRUN_TLE ||
+            $test['grader_message'] == IA_JRUN_WTLE) {
             echo '<td class="number">Depăşit</td>';
         } else {
             echo '<td class="number">'.$test['exec_time'].'ms</td>';
         }
-        if ($test["grader_message"] == "Memory limit exceeded.") {
+        if ($test['grader_message'] == IA_JRUN_MLE) {
             echo '<td class="number">Depăşit</td>';
         } else {
             echo '<td class="number">'.$test['mem_used'].'kb</td>';
