mirror of https://github.com/BOINC/boinc.git
530 lines
18 KiB
C++
530 lines
18 KiB
C++
// This file is part of BOINC.
|
|
// http://boinc.berkeley.edu
|
|
// Copyright (C) 2013 University of California
|
|
//
|
|
// BOINC is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU Lesser General Public License
|
|
// as published by the Free Software Foundation,
|
|
// either version 3 of the License, or (at your option) any later version.
|
|
//
|
|
// BOINC is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
// See the GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with BOINC. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
// Check whether a job can be sent to a host
|
|
|
|
#include "credit.h"
|
|
#include "sched_config.h"
|
|
#include "sched_hr.h"
|
|
#include "sched_main.h"
|
|
#include "sched_msgs.h"
|
|
#include "sched_send.h"
|
|
|
|
#include "sched_check.h"
|
|
|
|
#ifdef _USING_FCGI_
|
|
#include "boinc_fcgi.h"
|
|
#endif
|
|
|
|
const char* infeasible_string(int code) {
|
|
switch (code) {
|
|
case INFEASIBLE_MEM: return "Not enough memory";
|
|
case INFEASIBLE_DISK: return "Not enough disk";
|
|
case INFEASIBLE_CPU: return "CPU too slow";
|
|
case INFEASIBLE_APP_SETTING: return "App not selected";
|
|
case INFEASIBLE_WORKLOAD: return "Existing workload";
|
|
case INFEASIBLE_DUP: return "Already in reply";
|
|
case INFEASIBLE_HR: return "Homogeneous redundancy";
|
|
case INFEASIBLE_BANDWIDTH: return "Download bandwidth too low";
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
// Return true if the user has set application preferences,
|
|
// and excluded this app
|
|
//
|
|
bool app_not_selected(int appid) {
|
|
unsigned int i;
|
|
|
|
if (g_wreq->project_prefs.selected_apps.size() == 0) return false;
|
|
for (i=0; i<g_wreq->project_prefs.selected_apps.size(); i++) {
|
|
if (appid == g_wreq->project_prefs.selected_apps[i].appid) {
|
|
g_wreq->project_prefs.selected_apps[i].work_available = true;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static inline int check_memory(WORKUNIT& wu) {
|
|
double diff = wu.rsc_memory_bound - g_wreq->usable_ram;
|
|
if (diff > 0) {
|
|
char message[256];
|
|
sprintf(message,
|
|
"%s needs %0.2f MB RAM but only %0.2f MB is available for use.",
|
|
find_user_friendly_name(wu.appid),
|
|
wu.rsc_memory_bound/MEGA, g_wreq->usable_ram/MEGA
|
|
);
|
|
add_no_work_message(message);
|
|
|
|
if (config.debug_send_job) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[send_job] [WU#%lu %s] needs %0.2fMB RAM; [HOST#%lu] has %0.2fMB, %0.2fMB usable\n",
|
|
wu.id, wu.name, wu.rsc_memory_bound/MEGA,
|
|
g_reply->host.id, g_wreq->ram/MEGA, g_wreq->usable_ram/MEGA
|
|
);
|
|
}
|
|
g_wreq->mem.set_insufficient(wu.rsc_memory_bound);
|
|
g_reply->set_delay(DELAY_NO_WORK_TEMP);
|
|
return INFEASIBLE_MEM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int check_disk(WORKUNIT& wu) {
|
|
double diff = wu.rsc_disk_bound - g_wreq->disk_available;
|
|
if (diff > 0) {
|
|
char message[256];
|
|
sprintf(message,
|
|
"%s needs %0.2fMB more disk space. You currently have %0.2f MB available and it needs %0.2f MB.",
|
|
find_user_friendly_name(wu.appid),
|
|
diff/MEGA, g_wreq->disk_available/MEGA, wu.rsc_disk_bound/MEGA
|
|
);
|
|
add_no_work_message(message);
|
|
|
|
g_wreq->disk.set_insufficient(diff);
|
|
return INFEASIBLE_DISK;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int check_bandwidth(WORKUNIT& wu) {
|
|
if (wu.rsc_bandwidth_bound == 0) return 0;
|
|
|
|
// if n_bwdown is zero, the host has never downloaded anything,
|
|
// so skip this check
|
|
//
|
|
if (g_reply->host.n_bwdown == 0) return 0;
|
|
|
|
double diff = wu.rsc_bandwidth_bound - g_reply->host.n_bwdown;
|
|
if (diff > 0) {
|
|
char message[256];
|
|
sprintf(message,
|
|
"%s requires %0.2f KB/sec download bandwidth. Your computer has been measured at %0.2f KB/sec.",
|
|
find_user_friendly_name(wu.appid),
|
|
wu.rsc_bandwidth_bound/KILO, g_reply->host.n_bwdown/KILO
|
|
);
|
|
add_no_work_message(message);
|
|
|
|
g_wreq->bandwidth.set_insufficient(diff);
|
|
return INFEASIBLE_BANDWIDTH;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Determine if the app is "hard",
|
|
// and we should send it only to high-end hosts.
|
|
// Currently this is specified by setting weight=-1;
|
|
// this is a kludge for SETI@home/Astropulse.
|
|
//
|
|
static inline bool hard_app(APP& app) {
|
|
return (app.weight == -1);
|
|
}
|
|
|
|
static inline double get_estimated_delay(BEST_APP_VERSION& bav) {
|
|
int pt = bav.host_usage.proc_type;
|
|
if (pt == PROC_TYPE_CPU) {
|
|
return g_request->cpu_estimated_delay;
|
|
}
|
|
COPROC* cp = g_request->coprocs.proc_type_to_coproc(pt);
|
|
return cp->estimated_delay;
|
|
}
|
|
|
|
|
|
// return the delay bound to use for this job/host.
|
|
// Actually, return two: optimistic (lower) and pessimistic (higher).
|
|
// If the deadline check with the optimistic bound fails,
|
|
// try the pessimistic bound.
|
|
// TODO: clean up this mess
|
|
//
|
|
static void get_delay_bound_range(
|
|
WORKUNIT& wu,
|
|
int res_server_state, int res_priority, double res_report_deadline,
|
|
BEST_APP_VERSION& bav,
|
|
double& opt, double& pess
|
|
) {
|
|
if (res_server_state == RESULT_SERVER_STATE_IN_PROGRESS) {
|
|
double now = dtime();
|
|
if (res_report_deadline < now) {
|
|
// if original deadline has passed, return zeros
|
|
// This will skip deadline check.
|
|
opt = pess = 0;
|
|
return;
|
|
}
|
|
opt = res_report_deadline - now;
|
|
pess = wu.delay_bound;
|
|
} else {
|
|
opt = pess = wu.delay_bound;
|
|
|
|
// If the workunit needs reliable and is being sent to a reliable host,
|
|
// then shorten the delay bound by the percent specified
|
|
//
|
|
if (config.reliable_on_priority && res_priority >= config.reliable_on_priority && config.reliable_reduced_delay_bound > 0.01
|
|
) {
|
|
opt = wu.delay_bound*config.reliable_reduced_delay_bound;
|
|
double est_wallclock_duration = estimate_duration(wu, bav);
|
|
|
|
// Check to see how reasonable this reduced time is.
|
|
// Increase it to twice the estimated delay bound
|
|
// if all the following apply:
|
|
//
|
|
// 1) Twice the estimate is longer then the reduced delay bound
|
|
// 2) Twice the estimate is less then the original delay bound
|
|
// 3) Twice the estimate is less then the twice the reduced delay bound
|
|
if (est_wallclock_duration*2 > opt
|
|
&& est_wallclock_duration*2 < wu.delay_bound
|
|
&& est_wallclock_duration*2 < wu.delay_bound*config.reliable_reduced_delay_bound*2
|
|
) {
|
|
opt = est_wallclock_duration*2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// return 0 if the job, with the given delay bound,
|
|
// will complete by its deadline, and won't cause other jobs to miss deadlines.
|
|
//
|
|
static inline int check_deadline(
|
|
WORKUNIT& wu, APP& app, BEST_APP_VERSION& bav
|
|
) {
|
|
if (config.ignore_delay_bound) return 0;
|
|
|
|
// skip delay check if host currently doesn't have any work
|
|
// and it's not a hard app.
|
|
// (i.e. everyone gets one result, no matter how slow they are)
|
|
//
|
|
if (get_estimated_delay(bav) == 0 && !hard_app(app)) {
|
|
if (config.debug_send_job) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[send_job] [WU#%lu] est delay 0, skipping deadline check\n",
|
|
wu.id
|
|
);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// if it's a hard app, don't send it to a host with no credit
|
|
//
|
|
if (hard_app(app) && g_reply->host.total_credit == 0) {
|
|
return INFEASIBLE_CPU;
|
|
}
|
|
|
|
// do EDF simulation if possible; else use cruder approximation
|
|
//
|
|
if (config.workload_sim && g_request->have_other_results_list) {
|
|
double est_dur = estimate_duration(wu, bav);
|
|
if (g_reply->wreq.edf_reject_test(est_dur, wu.delay_bound)) {
|
|
return INFEASIBLE_WORKLOAD;
|
|
}
|
|
IP_RESULT candidate("", wu.delay_bound, est_dur);
|
|
safe_strcpy(candidate.name, wu.name);
|
|
if (check_candidate(candidate, g_wreq->effective_ncpus, g_request->ip_results)) {
|
|
// it passed the feasibility test,
|
|
// but don't add it to the workload yet;
|
|
// wait until we commit to sending it
|
|
} else {
|
|
g_reply->wreq.edf_reject(est_dur, wu.delay_bound);
|
|
g_reply->wreq.speed.set_insufficient(0);
|
|
return INFEASIBLE_WORKLOAD;
|
|
}
|
|
} else {
|
|
double ewd = estimate_duration(wu, bav);
|
|
if (hard_app(app)) ewd *= 1.3;
|
|
double est_report_delay = get_estimated_delay(bav) + ewd;
|
|
double diff = est_report_delay - wu.delay_bound;
|
|
if (diff > 0) {
|
|
if (config.debug_send_job) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[send_job] [WU#%lu] deadline miss %d > %d\n",
|
|
wu.id, (int)est_report_delay, wu.delay_bound
|
|
);
|
|
}
|
|
g_reply->wreq.speed.set_insufficient(diff);
|
|
return INFEASIBLE_CPU;
|
|
} else {
|
|
if (config.debug_send_job) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[send_job] [WU#%lu] meets deadline: %.2f + %.2f < %d\n",
|
|
wu.id, get_estimated_delay(bav), ewd, wu.delay_bound
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Fast checks (no DB access) to see if the job can be sent to the host.
|
|
// Reasons why not include:
|
|
// 1) the host doesn't have enough memory;
|
|
// 2) the host doesn't have enough disk space;
|
|
// 3) based on CPU speed, resource share and estimated delay,
|
|
// the host probably won't get the result done within the delay bound
|
|
// 4) app isn't in user's "approved apps" list
|
|
//
|
|
// If the job is feasible, return 0 and fill in wu.delay_bound
|
|
// with the delay bound we've decided to use.
|
|
//
|
|
int wu_is_infeasible_fast(
|
|
WORKUNIT& wu,
|
|
int res_server_state, int res_priority, double res_report_deadline,
|
|
APP& app, BEST_APP_VERSION& bav
|
|
) {
|
|
int retval;
|
|
|
|
// project-specific check
|
|
//
|
|
if (wu_is_infeasible_custom(wu, app, bav)) {
|
|
return INFEASIBLE_CUSTOM;
|
|
}
|
|
|
|
if (config.user_filter) {
|
|
if (wu.batch && wu.batch != g_reply->user.id) {
|
|
return INFEASIBLE_USER_FILTER;
|
|
}
|
|
}
|
|
|
|
// homogeneous redundancy: can't send if app uses HR and
|
|
// 1) host is of unknown HR class, or
|
|
// 2) WU is already committed to different HR class
|
|
//
|
|
if (app_hr_type(app)) {
|
|
if (hr_unknown_class(g_reply->host, app_hr_type(app))) {
|
|
if (config.debug_send_job) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[send_job] [HOST#%lu] [WU#%lu %s] host is of unknown class in HR type %d\n",
|
|
g_reply->host.id, wu.id, wu.name, app_hr_type(app)
|
|
);
|
|
}
|
|
return INFEASIBLE_HR;
|
|
}
|
|
if (already_sent_to_different_hr_class(wu, app)) {
|
|
if (config.debug_send_job) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[send_job] [HOST#%lu] [WU#%lu %s] failed quick HR check: WU is class %d, host is class %d\n",
|
|
g_reply->host.id, wu.id, wu.name, wu.hr_class, hr_class(g_request->host, app_hr_type(app))
|
|
);
|
|
}
|
|
return INFEASIBLE_HR;
|
|
}
|
|
}
|
|
|
|
// homogeneous app version
|
|
//
|
|
if (app.homogeneous_app_version) {
|
|
DB_ID_TYPE avid = wu.app_version_id;
|
|
if (avid && bav.avp->id != avid) {
|
|
if (config.debug_send_job) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[send_job] [HOST#%lu] [WU#%lu %s] failed homogeneous app version check: %lu %lu\n",
|
|
g_reply->host.id, wu.id, wu.name, avid, bav.avp->id
|
|
);
|
|
}
|
|
return INFEASIBLE_HAV;
|
|
}
|
|
}
|
|
|
|
if (config.one_result_per_user_per_wu || config.one_result_per_host_per_wu) {
|
|
if (wu_already_in_reply(wu)) {
|
|
return INFEASIBLE_DUP;
|
|
}
|
|
}
|
|
|
|
retval = check_memory(wu);
|
|
if (retval) return retval;
|
|
retval = check_disk(wu);
|
|
if (retval) return retval;
|
|
retval = check_bandwidth(wu);
|
|
if (retval) return retval;
|
|
|
|
if (app.non_cpu_intensive) {
|
|
return 0;
|
|
}
|
|
|
|
// do deadline check last because EDF sim uses some CPU
|
|
//
|
|
double opt, pess;
|
|
get_delay_bound_range(
|
|
wu, res_server_state, res_priority, res_report_deadline, bav, opt, pess
|
|
);
|
|
wu.delay_bound = (int)opt;
|
|
if (opt == 0) {
|
|
// this is a resend; skip deadline check
|
|
return 0;
|
|
}
|
|
retval = check_deadline(wu, app, bav);
|
|
if (retval && (opt != pess)) {
|
|
wu.delay_bound = (int)pess;
|
|
retval = check_deadline(wu, app, bav);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
// Do checks that require DB access for whether we can send this job,
|
|
// and return:
|
|
// CHECK_OK if OK to send
|
|
// CHECK_NO_HOST if can't send to this host
|
|
// CHECK_NO_ANY if can't send to ANY host
|
|
// e.g. WU error mask is nonzero
|
|
//
|
|
int slow_check(
|
|
WU_RESULT& wu_result, // the job cache entry.
|
|
// We may refresh its hr_class and app_version_id fields.
|
|
APP* app,
|
|
BEST_APP_VERSION* bavp // the app version to be used
|
|
) {
|
|
int retval;
|
|
long n;
|
|
DB_RESULT result;
|
|
char buf[256];
|
|
WORKUNIT& wu = wu_result.workunit;
|
|
|
|
// Don't send if we've already sent a result of this WU to this user.
|
|
//
|
|
if (config.one_result_per_user_per_wu) {
|
|
sprintf(buf,
|
|
"where workunitid=%lu and userid=%lu", wu.id, g_reply->user.id
|
|
);
|
|
retval = result.count(n, buf);
|
|
if (retval) {
|
|
log_messages.printf(MSG_CRITICAL,
|
|
"send_work: can't get result count (%s)\n", boincerror(retval)
|
|
);
|
|
return CHECK_NO_HOST;
|
|
} else {
|
|
if (n>0) {
|
|
if (config.debug_send_job) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[send_job] [USER#%lu] already has %ld result(s) for [WU#%lu]\n",
|
|
g_reply->user.id, n, wu.id
|
|
);
|
|
}
|
|
return CHECK_NO_HOST;
|
|
}
|
|
}
|
|
} else if (config.one_result_per_host_per_wu) {
|
|
// Don't send if we've already sent a result of this WU to this host.
|
|
// We only have to check this if we don't send one result per user.
|
|
//
|
|
sprintf(buf,
|
|
"where workunitid=%lu and hostid=%lu", wu.id, g_reply->host.id
|
|
);
|
|
retval = result.count(n, buf);
|
|
if (retval) {
|
|
log_messages.printf(MSG_CRITICAL,
|
|
"send_work: can't get result count (%s)\n", boincerror(retval)
|
|
);
|
|
return CHECK_NO_HOST;
|
|
} else {
|
|
if (n>0) {
|
|
if (config.debug_send_job) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[send_job] [HOST#%lu] already has %ld result(s) for [WU#%lu]\n",
|
|
g_reply->host.id, n, wu.id
|
|
);
|
|
}
|
|
return CHECK_NO_HOST;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Checks that require looking up the WU.
|
|
// Lump these together so we only do 1 lookup
|
|
//
|
|
if (app_hr_type(*app) || app->homogeneous_app_version) {
|
|
DB_WORKUNIT db_wu;
|
|
db_wu.id = wu.id;
|
|
int vals[3];
|
|
retval = db_wu.get_field_ints(
|
|
"hr_class, app_version_id, error_mask", 3, vals
|
|
);
|
|
if (retval) {
|
|
log_messages.printf(MSG_CRITICAL,
|
|
"can't get fields for [WU#%lu]: %s\n", db_wu.id, boincerror(retval)
|
|
);
|
|
return CHECK_NO_HOST;
|
|
}
|
|
|
|
// check wu.error_mask
|
|
//
|
|
if (vals[2] != 0) {
|
|
return CHECK_NO_ANY;
|
|
}
|
|
|
|
if (app_hr_type(*app)) {
|
|
wu.hr_class = vals[0];
|
|
if (already_sent_to_different_hr_class(wu, *app)) {
|
|
if (config.debug_send_job) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[send_job] [HOST#%lu] [WU#%lu %s] is assigned to different HR class\n",
|
|
g_reply->host.id, wu.id, wu.name
|
|
);
|
|
}
|
|
// Mark the workunit as infeasible.
|
|
// This ensures that jobs already assigned to an HR class
|
|
// are processed first.
|
|
//
|
|
wu_result.infeasible_count++;
|
|
return CHECK_NO_HOST;
|
|
}
|
|
}
|
|
if (app->homogeneous_app_version) {
|
|
int wu_avid = vals[1];
|
|
wu.app_version_id = wu_avid;
|
|
if (wu_avid && wu_avid != bavp->avp->id) {
|
|
if (config.debug_send_job) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[send_job] [HOST#%lu] [WU#%lu %s] is assigned to different app version\n",
|
|
g_reply->host.id, wu.id, wu.name
|
|
);
|
|
}
|
|
wu_result.infeasible_count++;
|
|
return CHECK_NO_HOST;
|
|
}
|
|
}
|
|
}
|
|
return CHECK_OK;
|
|
}
|
|
|
|
// Check for pathological conditions that mean
|
|
// result is not sendable at all.
|
|
//
|
|
bool result_still_sendable(DB_RESULT& result, WORKUNIT& wu) {
|
|
int retval = result.lookup_id(result.id);
|
|
if (retval) {
|
|
log_messages.printf(MSG_CRITICAL,
|
|
"[RESULT#%lu] result.lookup_id() failed: %s\n",
|
|
result.id, boincerror(retval)
|
|
);
|
|
return false;
|
|
}
|
|
if (result.server_state != RESULT_SERVER_STATE_UNSENT) {
|
|
log_messages.printf(MSG_NORMAL,
|
|
"[RESULT#%lu] expected to be unsent; instead, state is %d\n",
|
|
result.id, result.server_state
|
|
);
|
|
return false;
|
|
}
|
|
if (result.workunitid != wu.id) {
|
|
log_messages.printf(MSG_CRITICAL,
|
|
"[RESULT#%lu] wrong WU ID: wanted %lu, got %lu\n",
|
|
result.id, wu.id, result.workunitid
|
|
);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|