boinc/client/work_fetch.C

811 lines
25 KiB
C

// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2008 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/>.
// High-level logic for communicating with scheduling servers,
// and for merging the result of a scheduler RPC into the client state
// The scheduler RPC mechanism is in scheduler_op.C
#include "cpp.h"
#ifdef _WIN32
#include "boinc_win.h"
#endif
#ifndef _WIN32
#include "config.h"
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <strings.h>
#include <map>
#include <set>
#endif
#include "error_numbers.h"
#include "file_names.h"
#include "filesys.h"
#include "parse.h"
#include "str_util.h"
#include "util.h"
#include "client_msgs.h"
#include "scheduler_op.h"
#ifdef SIM
#include "sim.h"
#else
#include "client_state.h"
#endif
using std::max;
using std::vector;
using std::string;
// quantities like avg CPU time decay by a factor of e every week
//
#define EXP_DECAY_RATE (1./(SECONDS_PER_DAY*7))
// try to report results this much before their deadline
//
#define REPORT_DEADLINE_CUSHION ((double)SECONDS_PER_DAY)
static const char* urgency_name(int urgency) {
switch(urgency) {
case WORK_FETCH_DONT_NEED: return "Don't need";
case WORK_FETCH_OK: return "OK";
case WORK_FETCH_NEED: return "Need";
case WORK_FETCH_NEED_IMMEDIATELY: return "Need immediately";
}
return "Unknown";
}
// how many CPUs should this project occupy on average,
// based on its resource share relative to a given set
//
int CLIENT_STATE::proj_min_results(PROJECT* p, double subset_resource_share) {
if (p->non_cpu_intensive) {
return 1;
}
if (!subset_resource_share) return 1; // TODO - fix
return (int)(ceil(ncpus*p->resource_share/subset_resource_share));
}
void CLIENT_STATE::check_project_timeout() {
unsigned int i;
for (i=0; i<projects.size(); i++) {
PROJECT* p = projects[i];
if (p->possibly_backed_off && now > p->min_rpc_time) {
p->possibly_backed_off = false;
request_work_fetch("Project backoff ended");
}
}
}
void PROJECT::set_min_rpc_time(double future_time, const char* reason) {
if (future_time > min_rpc_time) {
min_rpc_time = future_time;
possibly_backed_off = true;
if (log_flags.sched_op_debug) {
msg_printf(this, MSG_INFO,
"[sched_op_debug] Deferring communication for %s",
timediff_format(min_rpc_time - gstate.now).c_str()
);
msg_printf(this, MSG_INFO, "[sched_op_debug] Reason: %s\n", reason);
}
}
}
// Return true if we should not contact the project yet.
//
bool PROJECT::waiting_until_min_rpc_time() {
return (min_rpc_time > gstate.now);
}
// find a project that needs to have its master file fetched
//
PROJECT* CLIENT_STATE::next_project_master_pending() {
unsigned int i;
PROJECT* p;
for (i=0; i<projects.size(); i++) {
p = projects[i];
if (p->waiting_until_min_rpc_time()) continue;
if (p->suspended_via_gui) continue;
if (p->master_url_fetch_pending) {
return p;
}
}
return 0;
}
// find a project for which a scheduler RPC is pending
// and we're not backed off
//
PROJECT* CLIENT_STATE::next_project_sched_rpc_pending() {
unsigned int i;
PROJECT* p;
for (i=0; i<projects.size(); i++) {
p = projects[i];
if (p->waiting_until_min_rpc_time()) continue;
if (p->next_rpc_time && p->next_rpc_time<now) {
p->sched_rpc_pending = RPC_REASON_PROJECT_REQ;
p->next_rpc_time = 0;
}
// if (p->suspended_via_gui) continue;
// do the RPC even if suspended.
// This is critical for acct mgrs, to propagate new host CPIDs
//
if (p->sched_rpc_pending) {
return p;
}
}
return 0;
}
PROJECT* CLIENT_STATE::next_project_trickle_up_pending() {
unsigned int i;
PROJECT* p;
for (i=0; i<projects.size(); i++) {
p = projects[i];
if (p->waiting_until_min_rpc_time()) continue;
if (p->suspended_via_gui) continue;
if (p->trickle_up_pending) {
return p;
}
}
return 0;
}
// Return the best project to fetch work from, NULL if none
//
// Pick the one with largest (long term debt - amount of current work)
//
// PRECONDITIONS:
// - work_request_urgency and work_request set for all projects
// - CLIENT_STATE::overall_work_fetch_urgency is set
// (by previous call to compute_work_requests())
//
PROJECT* CLIENT_STATE::next_project_need_work() {
PROJECT *p, *p_prospect = NULL;
unsigned int i;
for (i=0; i<projects.size(); i++) {
p = projects[i];
if (p->work_request_urgency == WORK_FETCH_DONT_NEED) continue;
if (p->work_request == 0) continue;
if (!p->contactable()) continue;
// if we don't need work, only get work from non-cpu intensive projects.
//
if (overall_work_fetch_urgency == WORK_FETCH_DONT_NEED && !p->non_cpu_intensive) continue;
// if we don't really need work,
// and we don't really need work from this project, pass.
//
if (overall_work_fetch_urgency == WORK_FETCH_OK) {
if (p->work_request_urgency <= WORK_FETCH_OK) {
continue;
}
}
if (p_prospect) {
if (p->work_request_urgency == WORK_FETCH_OK &&
p_prospect->work_request_urgency > WORK_FETCH_OK
) {
continue;
}
if (p->long_term_debt + p->rr_sim_status.cpu_shortfall < p_prospect->long_term_debt + p_prospect->rr_sim_status.cpu_shortfall
&& !p->non_cpu_intensive
) {
continue;
}
}
p_prospect = p;
}
if (p_prospect && (p_prospect->work_request <= 0)) {
p_prospect->work_request = 1.0;
if (log_flags.work_fetch_debug) {
msg_printf(0, MSG_INFO,
"[work_fetch_debug] next_project_need_work: project picked %s",
p_prospect->project_name
);
}
}
return p_prospect;
}
// find a project with finished results that should be reported.
// This means:
// - we're not backing off contacting the project
// - the result is ready_to_report (compute done; files uploaded)
// - we're within a day of the report deadline,
// or at least a day has elapsed since the result was completed,
// or we have a sporadic connection
//
PROJECT* CLIENT_STATE::find_project_with_overdue_results() {
unsigned int i;
RESULT* r;
for (i=0; i<results.size(); i++) {
r = results[i];
if (!r->ready_to_report) continue;
PROJECT* p = r->project;
if (p->waiting_until_min_rpc_time()) continue;
if (p->suspended_via_gui) continue;
if (config.report_results_immediately) {
return p;
}
if (net_status.have_sporadic_connection) {
return p;
}
double cushion = std::max(REPORT_DEADLINE_CUSHION, work_buf_min());
if (gstate.now > r->report_deadline - cushion) {
return p;
}
if (gstate.now > r->completed_time + SECONDS_PER_DAY) {
return p;
}
}
return 0;
}
// the fraction of time a given CPU is working for BOINC
//
double CLIENT_STATE::overall_cpu_frac() {
double running_frac = time_stats.on_frac * time_stats.active_frac * time_stats.cpu_efficiency;
if (running_frac < 0.01) running_frac = 0.01;
if (running_frac > 1) running_frac = 1;
return running_frac;
}
// the expected number of CPU seconds completed by the client
// in a second of wall-clock time.
// May be > 1 on a multiprocessor.
//
double CLIENT_STATE::avg_proc_rate() {
return ncpus*overall_cpu_frac();
}
// estimate wall-clock time until the number of uncompleted results
// for project p will reach k,
// given the total resource share of a set of competing projects
//
double CLIENT_STATE::time_until_work_done(
PROJECT *p, int k, double subset_resource_share
) {
int num_results_to_skip = k;
double est = 0;
// total up the estimated time for this project's unstarted
// and partially completed results,
// omitting the last k
//
for (vector<RESULT*>::reverse_iterator iter = results.rbegin();
iter != results.rend(); iter++
) {
RESULT *rp = *iter;
if (rp->project != p
|| rp->state() > RESULT_FILES_DOWNLOADED
|| rp->ready_to_report
) continue;
if (num_results_to_skip > 0) {
--num_results_to_skip;
continue;
}
if (rp->project->non_cpu_intensive) {
// if it is a non_cpu intensive project,
// it needs only one at a time.
//
est = max(rp->estimated_cpu_time_remaining(true), work_buf_min());
} else {
est += rp->estimated_cpu_time_remaining(true);
}
}
if (log_flags.work_fetch_debug) {
msg_printf(NULL, MSG_INFO,
"[work_fetch_debug] time_until_work_done(): est %f ssr %f apr %f prs %f",
est, subset_resource_share, avg_proc_rate(), p->resource_share
);
}
if (subset_resource_share) {
double apr = avg_proc_rate()*p->resource_share/subset_resource_share;
return est/apr;
} else {
return est/avg_proc_rate(); // TODO - fix
}
}
// Top-level function for work fetch policy.
// Outputs:
// - overall_work_fetch_urgency
// - for each contactable project:
// - work_request and work_request_urgency
//
// Notes:
// - at most 1 CPU-intensive project will have a nonzero work_request
// and a work_request_urgency higher than DONT_NEED.
// This prevents projects with low LTD from getting work
// even though there was a higher LTD project that should get work.
// - all non-CPU-intensive projects that need work
// and are contactable will have a work request of 1.
//
// return false
//
bool CLIENT_STATE::compute_work_requests() {
unsigned int i;
static double last_time = 0;
if (gstate.now - last_time >= 60) {
gstate.request_work_fetch("timer");
}
if (!must_check_work_fetch) return false;
if (log_flags.work_fetch_debug) {
msg_printf(0, MSG_INFO, "[work_fetch_debug] compute_work_requests(): start");
}
last_time = gstate.now;
must_check_work_fetch = false;
compute_nuploading_results();
adjust_debts();
#ifdef SIM
if (work_fetch_old) {
// "dumb" version for simulator only.
// for each project, compute extra work needed to bring it up to
// total_buf/relative resource share.
//
overall_work_fetch_urgency = WORK_FETCH_DONT_NEED;
double trs = total_resource_share();
double total_buf = ncpus*(work_buf_min() + work_buf_additional());
for (i=0; i<projects.size(); i++) {
PROJECT* p = projects[i];
double d = 0;
for (unsigned int j=0; j<results.size(); j++) {
RESULT* rp = results[j];
if (rp->project != p) continue;
d += rp->estimated_cpu_time_remaining(true);
}
double rrs = p->resource_share/trs;
double minq = total_buf*rrs;
if (d < minq) {
p->work_request = minq-d;
p->work_request_urgency = WORK_FETCH_NEED;
overall_work_fetch_urgency = WORK_FETCH_NEED;
} else {
p->work_request = 0;
p->work_request_urgency = WORK_FETCH_DONT_NEED;
}
}
return false;
}
#endif
rr_simulation();
// compute per-project and overall urgency
//
bool possible_deadline_miss = false;
bool project_shortfall = false;
bool non_cpu_intensive_needs_work = false;
for (i=0; i< projects.size(); i++) {
PROJECT* p = projects[i];
if (p->non_cpu_intensive) {
if (p->nearly_runnable() || !p->contactable() || p->some_result_suspended()) {
p->work_request = 0;
p->work_request_urgency = WORK_FETCH_DONT_NEED;
} else {
p->work_request = 1.0;
p->work_request_urgency = WORK_FETCH_NEED_IMMEDIATELY;
non_cpu_intensive_needs_work = true;
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO,
"[work_fetch_debug] non-CPU-intensive project needs work"
);
}
return false;
}
} else {
p->work_request_urgency = WORK_FETCH_DONT_NEED;
p->work_request = 0;
if (p->rr_sim_status.deadlines_missed) {
possible_deadline_miss = true;
}
if (p->rr_sim_status.cpu_shortfall && p->long_term_debt > -global_prefs.cpu_scheduling_period()) {
project_shortfall = true;
}
}
}
if (cpu_shortfall <= 0.0 && (possible_deadline_miss || !project_shortfall)) {
overall_work_fetch_urgency = WORK_FETCH_DONT_NEED;
} else if (no_work_for_a_cpu()) {
overall_work_fetch_urgency = WORK_FETCH_NEED_IMMEDIATELY;
} else if (cpu_shortfall > 0) {
overall_work_fetch_urgency = WORK_FETCH_NEED;
} else {
overall_work_fetch_urgency = WORK_FETCH_OK;
}
if (log_flags.work_fetch_debug) {
msg_printf(0, MSG_INFO,
"[work_fetch_debug] compute_work_requests(): cpu_shortfall %f, overall urgency %s",
cpu_shortfall, urgency_name(overall_work_fetch_urgency)
);
}
if (overall_work_fetch_urgency == WORK_FETCH_DONT_NEED) {
if (non_cpu_intensive_needs_work) {
overall_work_fetch_urgency = WORK_FETCH_NEED_IMMEDIATELY;
}
return false;
}
// loop over projects, and pick one to get work from
//
double prrs = fetchable_resource_share();
PROJECT *pbest = NULL;
for (i=0; i<projects.size(); i++) {
PROJECT *p = projects[i];
// see if this project can be ruled out completely
//
if (p->non_cpu_intensive) continue;
if (!p->contactable()) {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO, "[work_fetch_debug] work fetch: project not contactable; skipping");
}
continue;
}
if ((p->deadlines_missed >= ncpus)
&& overall_work_fetch_urgency < WORK_FETCH_NEED
) {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO,
"[work_fetch_debug] project has %d deadline misses; skipping",
p->deadlines_missed
);
}
continue;
}
if (p->some_download_stalled()) {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO,
"[work_fetch_debug] project has stalled download; skipping"
);
}
continue;
}
if (p->some_result_suspended()) {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO, "[work_fetch_debug] project has suspended result; skipping");
}
continue;
}
if (p->overworked() && overall_work_fetch_urgency < WORK_FETCH_NEED) {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO, "[work_fetch_debug] project is overworked; skipping");
}
continue;
}
if (p->rr_sim_status.cpu_shortfall == 0.0 && overall_work_fetch_urgency < WORK_FETCH_NEED) {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO, "[work_fetch_debug] project has no shortfall; skipping");
}
continue;
}
if (p->nuploading_results > 2*ncpus) {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO,
"[work_fetch_debug] project has %d uploading results; skipping",
p->nuploading_results
);
}
continue;
}
// If the project's DCF is outside of reasonable limits,
// the project's WU FLOP estimates are not useful for predicting
// completion time.
// Switch to a simpler policy: ask for 1 sec of work if
// we don't have any.
//
if (p->duration_correction_factor < 0.02 || p->duration_correction_factor > 80.0) {
if (p->runnable()) {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO,
"[work_fetch_debug] project DCF %f out of range and have work; skipping",
p->duration_correction_factor
);
}
continue;
} else {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO,
"[work_fetch_debug] project DCF %f out of range: changing shortfall %f to 1.0",
p->duration_correction_factor, p->rr_sim_status.cpu_shortfall
);
}
p->rr_sim_status.cpu_shortfall = 1.0;
}
}
// see if this project is better than our current best
//
if (pbest) {
// avoid getting work from a project in deadline trouble
//
if (p->deadlines_missed && !pbest->deadlines_missed) {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO,
"[work_fetch_debug] project has deadline misses, %s doesn't",
pbest->get_project_name()
);
}
continue;
}
// avoid getting work from an overworked project
//
if (p->overworked() && !pbest->overworked()) {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO,
"[work_fetch_debug] project is overworked, %s isn't",
pbest->get_project_name()
);
}
continue;
}
// get work from project with highest LTD
//
if (pbest->long_term_debt + pbest->rr_sim_status.cpu_shortfall > p->long_term_debt + p->rr_sim_status.cpu_shortfall) {
if (log_flags.work_fetch_debug) {
msg_printf(p, MSG_INFO,
"[work_fetch_debug] project has less LTD than %s",
pbest->get_project_name()
);
}
continue;
}
}
pbest = p;
if (log_flags.work_fetch_debug) {
msg_printf(pbest, MSG_INFO, "[work_fetch_debug] best project so far");
}
}
if (pbest) {
pbest->work_request = max(
pbest->rr_sim_status.cpu_shortfall,
cpu_shortfall * (prrs ? pbest->resource_share/prrs : 1)
);
// sanity check
//
double x = 1.01*work_buf_total()*ncpus;
// the 1.01 is for round-off error
if (pbest->work_request > x) {
msg_printf(NULL, MSG_INTERNAL_ERROR,
"Proposed work request %f bigger than max %f",
pbest->work_request, x
);
pbest->work_request = x;
}
if (!pbest->nearly_runnable()) {
pbest->work_request_urgency = WORK_FETCH_NEED_IMMEDIATELY;
} else if (pbest->rr_sim_status.cpu_shortfall) {
pbest->work_request_urgency = WORK_FETCH_NEED;
} else {
pbest->work_request_urgency = WORK_FETCH_OK;
}
if (log_flags.work_fetch_debug) {
msg_printf(pbest, MSG_INFO,
"[work_fetch_debug] compute_work_requests(): work req %f, shortfall %f, urgency %s\n",
pbest->work_request, pbest->rr_sim_status.cpu_shortfall,
urgency_name(pbest->work_request_urgency)
);
}
} else if (non_cpu_intensive_needs_work) {
overall_work_fetch_urgency = WORK_FETCH_NEED_IMMEDIATELY;
}
return false;
}
// called when benchmarks change
//
void CLIENT_STATE::scale_duration_correction_factors(double factor) {
if (factor <= 0) return;
for (unsigned int i=0; i<projects.size(); i++) {
PROJECT* p = projects[i];
p->duration_correction_factor *= factor;
}
if (log_flags.cpu_sched_debug) {
msg_printf(NULL, MSG_INFO,
"[cpu_sched_debug] scaling duration correction factors by %f",
factor
);
}
}
// Choose a new host CPID.
// If using account manager, do scheduler RPCs
// to all acct-mgr-attached projects to propagate the CPID
//
void CLIENT_STATE::generate_new_host_cpid() {
host_info.generate_host_cpid();
for (unsigned int i=0; i<projects.size(); i++) {
if (projects[i]->attached_via_acct_mgr) {
projects[i]->sched_rpc_pending = RPC_REASON_ACCT_MGR_REQ;
projects[i]->set_min_rpc_time(now + 15, "Sending new host CPID");
}
}
}
void CLIENT_STATE::compute_nuploading_results() {
unsigned int i;
for (i=0; i<projects.size(); i++) {
projects[i]->nuploading_results = 0;
}
for (i=0; i<results.size(); i++) {
RESULT* rp = results[i];
if (rp->state() == RESULT_FILES_UPLOADING) {
rp->project->nuploading_results++;
}
}
}
bool PROJECT::runnable() {
if (suspended_via_gui) return false;
for (unsigned int i=0; i<gstate.results.size(); i++) {
RESULT* rp = gstate.results[i];
if (rp->project != this) continue;
if (rp->runnable()) return true;
}
return false;
}
bool PROJECT::downloading() {
if (suspended_via_gui) return false;
for (unsigned int i=0; i<gstate.results.size(); i++) {
RESULT* rp = gstate.results[i];
if (rp->project != this) continue;
if (rp->downloading()) return true;
}
return false;
}
bool PROJECT::some_result_suspended() {
unsigned int i;
for (i=0; i<gstate.results.size(); i++) {
RESULT *rp = gstate.results[i];
if (rp->project != this) continue;
if (rp->suspended_via_gui) return true;
}
return false;
}
bool PROJECT::contactable() {
if (suspended_via_gui) return false;
if (master_url_fetch_pending) return false;
if (min_rpc_time > gstate.now) return false;
if (dont_request_more_work) return false;
return true;
}
bool PROJECT::potentially_runnable() {
if (runnable()) return true;
if (contactable()) return true;
if (downloading()) return true;
return false;
}
bool PROJECT::nearly_runnable() {
if (runnable()) return true;
if (downloading()) return true;
return false;
}
bool PROJECT::overworked() {
return long_term_debt < -gstate.global_prefs.cpu_scheduling_period();
}
bool RESULT::runnable() {
if (suspended_via_gui) return false;
if (project->suspended_via_gui) return false;
if (state() != RESULT_FILES_DOWNLOADED) return false;
return true;
}
bool RESULT::nearly_runnable() {
return runnable() || downloading();
}
// Return true if the result is waiting for its files to download,
// and nothing prevents this from happening soon
//
bool RESULT::downloading() {
if (suspended_via_gui) return false;
if (project->suspended_via_gui) return false;
if (state() > RESULT_FILES_DOWNLOADING) return false;
return true;
}
double RESULT::estimated_cpu_time_uncorrected() {
return wup->rsc_fpops_est/gstate.host_info.p_fpops;
}
// estimate how long a result will take on this host
//
double RESULT::estimated_cpu_time(bool for_work_fetch) {
#ifdef SIM
SIM_PROJECT* spp = (SIM_PROJECT*)project;
if (dual_dcf && for_work_fetch && spp->completions_ratio_mean) {
return estimated_cpu_time_uncorrected()*spp->completions_ratio_mean;
}
#endif
return estimated_cpu_time_uncorrected()*project->duration_correction_factor;
}
double RESULT::estimated_cpu_time_remaining(bool for_work_fetch) {
if (computing_done()) return 0;
ACTIVE_TASK* atp = gstate.lookup_active_task_by_result(this);
if (atp) {
return atp->est_cpu_time_to_completion(for_work_fetch);
}
return estimated_cpu_time(for_work_fetch);
}
// Returns the estimated CPU time to completion (in seconds) of this task.
// Compute this as a weighted average of estimates based on
// 1) the workunit's flops count
// 2) the current reported CPU time and fraction done
//
double ACTIVE_TASK::est_cpu_time_to_completion(bool for_work_fetch) {
if (fraction_done >= 1) return 0;
double wu_est = result->estimated_cpu_time(for_work_fetch);
if (fraction_done <= 0) return wu_est;
double frac_est = (current_cpu_time / fraction_done) - current_cpu_time;
double fraction_left = 1-fraction_done;
double x = fraction_done*frac_est + fraction_left*fraction_left*wu_est;
return x;
}
// trigger work fetch
//
void CLIENT_STATE::request_work_fetch(const char* where) {
if (log_flags.work_fetch_debug) {
msg_printf(0, MSG_INFO, "[work_fetch_debug] Request work fetch: %s", where);
}
must_check_work_fetch = true;
}
const char *BOINC_RCSID_d3a4a7711 = "$Id$";