// 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 . // Logic related to general (also known as global) preferences: // when to compute, how much disk to use, etc. // #include "cpp.h" #ifdef _WIN32 #include "boinc_win.h" #else #include "config.h" #if HAVE_SYS_STAT_H #include #endif #if HAVE_SYS_TYPES_H #include #endif #endif #include "common_defs.h" #include "filesys.h" #include "parse.h" #include "str_util.h" #include "str_replace.h" #include "util.h" #include "client_msgs.h" #include "client_state.h" #include "cpu_benchmark.h" #include "file_names.h" #include "project.h" using std::min; using std::string; #define MAX_PROJ_PREFS_LEN 65536 // max length of project-specific prefs // Return the maximum allowed disk usage as determined by user preferences. // There are three different settings in the prefs; // return the least of the three. // double CLIENT_STATE::allowed_disk_usage(double boinc_total) { double limit_pct, limit_min_free, limit_abs; limit_pct = host_info.d_total*global_prefs.disk_max_used_pct/100.0; limit_min_free = boinc_total + host_info.d_free - global_prefs.disk_min_free_gb*GIGA; double size = min(limit_pct, limit_min_free); if (global_prefs.disk_max_used_gb) { limit_abs = global_prefs.disk_max_used_gb*(GIGA); size = min(size, limit_abs); } if (size < 0) size = 0; return size; } #ifndef SIM // populate: // PROJECT::disk_usage for all projects // GLOBAL_STATE::client_disk_usage // GLOBAL_STATE::total_disk_usage // int CLIENT_STATE::get_disk_usages() { unsigned int i; double size; PROJECT* p; int retval; char buf[MAXPATHLEN]; client_disk_usage = 0; total_disk_usage = 0; for (i=0; idisk_usage = 0; retval = dir_size(p->project_dir(), size); if (!retval) p->disk_usage = size; } for (i=0; islot, buf, sizeof(buf)); retval = dir_size(buf, size); if (retval) continue; atp->wup->project->disk_usage += size; } for (i=0; idisk_usage; } retval = dir_size(".", size, false); if (!retval) { client_disk_usage = size; total_disk_usage += size; } return 0; } // populate PROJECT::disk_share for all projects, // i.e. the max space we should allocate to the project. // This is calculated as follows: // - each project has a "disk_resource_share" (DRS) // This is the resource share plus .1*(max resource share). // This ensures that backup projects get some disk. // - each project as a "desired_disk_usage (DDU)", // which is either its current usage // or an amount sent from the scheduler. // - each project has a "quota": (available space)*(drs/total_drs). // - a project is "greedy" if DDU > quota. // - if a project is non-greedy, share = quota // - X = available space - space used by non-greedy projects // - if a project is greedy, share = quota // + X*drs/(total drs of greedy projects) // void CLIENT_STATE::get_disk_shares() { PROJECT* p; unsigned int i; // compute disk resource shares // double trs = 0; double max_rs = 0; for (i=0; iddu = std::max(p->disk_usage, p->desired_disk_usage); double rs = p->resource_share; trs += rs; if (rs > max_rs) max_rs = rs; } if (trs) { max_rs /= 10; for (i=0; idisk_resource_share = p->resource_share + max_rs; } } else { for (i=0; idisk_resource_share = 1; } } // Compute: // greedy_drs: total disk resource share of greedy projects // non_greedy_ddu: total desired disk usage of non-greedy projects // double greedy_drs = 0; double non_greedy_ddu = 0; double allowed = allowed_disk_usage(total_disk_usage); for (i=0; idisk_quota = allowed*p->disk_resource_share/trs; if (p->ddu > p->disk_quota) { greedy_drs += p->disk_resource_share; } else { non_greedy_ddu += p->ddu; } } double greedy_allowed = allowed - non_greedy_ddu; if (log_flags.disk_usage_debug) { msg_printf(0, MSG_INFO, "[disk_usage] allowed %.2fMB used %.2fMB", allowed/MEGA, total_disk_usage/MEGA ); } for (i=0; idisk_resource_share/trs; if (p->ddu > allowed*rs) { p->disk_share = greedy_allowed*p->disk_resource_share/greedy_drs; } else { p->disk_share = p->disk_quota; } if (log_flags.disk_usage_debug) { msg_printf(p, MSG_INFO, "[disk_usage] usage %.2fMB share %.2fMB", p->disk_usage/MEGA, p->disk_share/MEGA ); } } } // See if we should suspend CPU and/or GPU processing; // return the CPU suspend_reason, // and if it's zero set gpu_suspend_reason // int CLIENT_STATE::check_suspend_processing() { if (benchmarks_running) { return SUSPEND_REASON_BENCHMARKS; } if (config.start_delay && now < time_stats.client_start_time + config.start_delay) { return SUSPEND_REASON_INITIAL_DELAY; } if (os_requested_suspend) { return SUSPEND_REASON_OS; } switch (cpu_run_mode.get_current()) { case RUN_MODE_ALWAYS: break; case RUN_MODE_NEVER: return SUSPEND_REASON_USER_REQ; default: // "run according to prefs" checks: // if (!global_prefs.run_on_batteries && host_info.host_is_running_on_batteries() ) { return SUSPEND_REASON_BATTERIES; } if (!global_prefs.run_if_user_active && user_active) { return SUSPEND_REASON_USER_ACTIVE; } if (global_prefs.cpu_times.suspended(now)) { return SUSPEND_REASON_TIME_OF_DAY; } if (global_prefs.suspend_if_no_recent_input) { bool idle = host_info.users_idle( check_all_logins, global_prefs.suspend_if_no_recent_input ); if (idle) { return SUSPEND_REASON_NO_RECENT_INPUT; } } if (now - exclusive_app_running < EXCLUSIVE_APP_WAIT) { return SUSPEND_REASON_EXCLUSIVE_APP_RUNNING; } if (global_prefs.suspend_cpu_usage && non_boinc_cpu_usage*100 > global_prefs.suspend_cpu_usage) { return SUSPEND_REASON_CPU_USAGE; } } // CPU throttling // if (global_prefs.cpu_usage_limit < 99) { // round-off? static double last_time=0, debt=0; double diff = now - last_time; last_time = now; if (diff >= POLL_INTERVAL/2. && diff < POLL_INTERVAL*10.) { debt += diff*global_prefs.cpu_usage_limit/100; if (debt < 0) { return SUSPEND_REASON_CPU_THROTTLE; } else { debt -= diff; } } } #ifdef ANDROID if (now > device_status_time + ANDROID_KEEPALIVE_TIMEOUT) { return SUSPEND_REASON_NO_GUI_KEEPALIVE; } // check for hot battery // if (device_status.battery_state == BATTERY_STATE_OVERHEATED) { return SUSPEND_REASON_BATTERY_OVERHEATED; } if (device_status.battery_temperature_celsius > global_prefs.battery_max_temperature) { return SUSPEND_REASON_BATTERY_OVERHEATED; } // on some devices, running jobs can drain the battery even // while it's recharging. // So compute only if 95% charged or more. // int cp = device_status.battery_charge_pct; if (cp >= 0) { if (cp < global_prefs.battery_charge_min_pct) { return SUSPEND_REASON_BATTERY_CHARGING; } } #endif // CPU is not suspended. See if GPUs are // if (!coprocs.none()) { int old_gpu_suspend_reason = gpu_suspend_reason; gpu_suspend_reason = 0; switch (gpu_run_mode.get_current()) { case RUN_MODE_ALWAYS: break; case RUN_MODE_NEVER: gpu_suspend_reason = SUSPEND_REASON_USER_REQ; break; default: if (now - exclusive_gpu_app_running < EXCLUSIVE_APP_WAIT) { gpu_suspend_reason = SUSPEND_REASON_EXCLUSIVE_APP_RUNNING; break; } if (user_active && !global_prefs.run_gpu_if_user_active) { gpu_suspend_reason = SUSPEND_REASON_USER_ACTIVE; break; } } if (old_gpu_suspend_reason && !gpu_suspend_reason) { if (log_flags.task) { msg_printf(NULL, MSG_INFO, "Resuming GPU computation"); } request_schedule_cpus("GPU resumption"); } else if (!old_gpu_suspend_reason && gpu_suspend_reason) { if (log_flags.task) { msg_printf(NULL, MSG_INFO, "Suspending GPU computation - %s", suspend_reason_string(gpu_suspend_reason) ); } request_schedule_cpus("GPU suspension"); } } return 0; } int CLIENT_STATE::suspend_tasks(int reason) { if (reason == SUSPEND_REASON_CPU_THROTTLE) { if (log_flags.cpu_sched) { msg_printf(NULL, MSG_INFO, "[cpu_sched] Suspending - CPU throttle"); } } else { if (log_flags.task) { msg_printf(NULL, MSG_INFO, "Suspending computation - %s", suspend_reason_string(reason) ); } } active_tasks.suspend_all(reason); return 0; } int CLIENT_STATE::resume_tasks(int reason) { if (reason == SUSPEND_REASON_CPU_THROTTLE) { if (log_flags.cpu_sched) { msg_printf(NULL, MSG_INFO, "[cpu_sched] Resuming - CPU throttle"); } active_tasks.unsuspend_all(); } else { if (log_flags.task) { msg_printf(NULL, MSG_INFO, "Resuming computation"); } active_tasks.unsuspend_all(); request_schedule_cpus("Resuming computation"); } return 0; } // Check whether to set network_suspended and file_xfers_suspended. // void CLIENT_STATE::check_suspend_network() { network_suspended = false; file_xfers_suspended = false; network_suspend_reason = 0; bool recent_rpc; // don't start network ops if system is shutting down // if (os_requested_suspend) { network_suspend_reason = SUSPEND_REASON_OS; network_suspended = true; goto done; } // no network traffic if we're allowing unsigned apps // if (config.unsigned_apps_ok) { network_suspended = true; file_xfers_suspended = true; network_suspend_reason = SUSPEND_REASON_USER_REQ; goto done; } // was there a recent GUI RPC that needs network? // recent_rpc = gui_rpcs.recent_rpc_needs_network( ALLOW_NETWORK_IF_RECENT_RPC_PERIOD ); switch(network_run_mode.get_current()) { case RUN_MODE_ALWAYS: goto done; case RUN_MODE_NEVER: file_xfers_suspended = true; if (!recent_rpc) network_suspended = true; network_suspend_reason = SUSPEND_REASON_USER_REQ; goto done; } #ifdef ANDROID if (now > device_status_time + ANDROID_KEEPALIVE_TIMEOUT) { file_xfers_suspended = true; if (!recent_rpc) network_suspended = true; network_suspend_reason = SUSPEND_REASON_NO_GUI_KEEPALIVE; } // use only WiFi // if (global_prefs.network_wifi_only && !device_status.wifi_online) { file_xfers_suspended = true; if (!recent_rpc) network_suspended = true; network_suspend_reason = SUSPEND_REASON_WIFI_STATE; } #endif if (global_prefs.daily_xfer_limit_mb && global_prefs.daily_xfer_period_days) { double up, down; daily_xfer_history.totals( global_prefs.daily_xfer_period_days, up, down ); if (up+down > global_prefs.daily_xfer_limit_mb*MEGA) { file_xfers_suspended = true; if (!recent_rpc) network_suspended = true; network_suspend_reason = SUSPEND_REASON_NETWORK_QUOTA_EXCEEDED; } } if (!global_prefs.run_if_user_active && user_active) { file_xfers_suspended = true; if (!recent_rpc) network_suspended = true; network_suspend_reason = SUSPEND_REASON_USER_ACTIVE; } if (global_prefs.net_times.suspended(now)) { file_xfers_suspended = true; if (!recent_rpc) network_suspended = true; network_suspend_reason = SUSPEND_REASON_TIME_OF_DAY; } if (now - exclusive_app_running < EXCLUSIVE_APP_WAIT) { file_xfers_suspended = true; if (!recent_rpc) network_suspended = true; network_suspend_reason = SUSPEND_REASON_EXCLUSIVE_APP_RUNNING; } done: if (log_flags.suspend_debug) { msg_printf(0, MSG_INFO, "[suspend] net_susp %d file_xfer_susp %d reason %d", network_suspended, file_xfers_suspended, network_suspend_reason ); } } #endif // ifndef SIM // call this only after parsing global prefs // PROJECT* CLIENT_STATE::global_prefs_source_project() { return lookup_project(global_prefs.source_project); } void CLIENT_STATE::show_global_prefs_source(bool found_venue) { PROJECT* pp = global_prefs_source_project(); if (pp) { msg_printf(pp, MSG_INFO, "General prefs: from %s (last modified %s)", pp->get_project_name(), time_to_string(global_prefs.mod_time) ); } else { msg_printf(NULL, MSG_INFO, "General prefs: from %s (last modified %s)", global_prefs.source_project, time_to_string(global_prefs.mod_time) ); } if (strlen(main_host_venue)) { msg_printf(pp, MSG_INFO, "Computer location: %s", main_host_venue); if (found_venue) { msg_printf(NULL, MSG_INFO, "General prefs: using separate prefs for %s", main_host_venue ); } else { msg_printf(pp, MSG_INFO, "General prefs: no separate prefs for %s; using your defaults", main_host_venue ); } } else { msg_printf(pp, MSG_INFO, "Host location: none"); msg_printf(pp, MSG_INFO, "General prefs: using your defaults"); } } // parse user's project preferences, // generating FILE_REF and FILE_INFO objects for each element. // int PROJECT::parse_preferences_for_user_files() { char buf[1024]; string timestamp, open_name, url, filename; FILE_INFO* fip; FILE_REF fr; user_files.clear(); size_t n=0, start, end; while (1) { start = project_specific_prefs.find("", n); if (start == string::npos) break; end = project_specific_prefs.find("", n); if (end == string::npos) break; start += strlen(""); string x = project_specific_prefs.substr(start, end); n = end + strlen(""); strlcpy(buf, x.c_str(), sizeof(buf)); if (!parse_str(buf, "", timestamp)) break; if (!parse_str(buf, "", open_name)) break; if (!parse_str(buf, "", url)) break; filename = open_name + "_" + timestamp; fip = gstate.lookup_file_info(this, filename.c_str()); if (!fip) { fip = new FILE_INFO; fip->project = this; fip->download_urls.add(url); safe_strcpy(fip->name, filename.c_str()); fip->is_user_file = true; gstate.file_infos.push_back(fip); } fr.file_info = fip; safe_strcpy(fr.open_name, open_name.c_str()); user_files.push_back(fr); } return 0; } // Read global preferences into the global_prefs structure. // 1) read the override file to get venue in case it's there // 2) read global_prefs.xml // 3) read the override file again // // This is called: // - on startup // - on completion of a scheduler or AMS RPC, if they sent prefs // - in response to read_global_prefs_override GUI RPC // void CLIENT_STATE::read_global_prefs( const char* fname, const char* override_fname ) { bool found_venue; bool venue_specified_in_override = false; int retval; FILE* f; string foo; if (override_fname) { retval = read_file_string(override_fname, foo); if (!retval) { parse_str(foo.c_str(), "", main_host_venue, sizeof(main_host_venue)); if (strlen(main_host_venue)) { venue_specified_in_override = true; } } } retval = global_prefs.parse_file( fname, main_host_venue, found_venue ); if (retval) { if (retval == ERR_FOPEN) { msg_printf(NULL, MSG_INFO, "No general preferences found - using defaults" ); } else { msg_printf(NULL, MSG_INFO, "Couldn't parse preferences file - using defaults" ); boinc_delete_file(fname); } global_prefs.init(); } else { if (!venue_specified_in_override) { // check that the source project's venue matches main_host_venue. // If not, read file again. // This is a fix for cases where main_host_venue is out of synch // PROJECT* p = global_prefs_source_project(); if (p && strcmp(main_host_venue, p->host_venue)) { safe_strcpy(main_host_venue, p->host_venue); global_prefs.parse_file(fname, main_host_venue, found_venue); } } show_global_prefs_source(found_venue); } // read the override file // global_prefs.override_file_present = false; if (override_fname) { f = fopen(override_fname, "r"); if (f) { MIOFILE mf; GLOBAL_PREFS_MASK mask; mf.init_file(f); XML_PARSER xp(&mf); global_prefs.parse_override(xp, "", found_venue, mask); msg_printf(NULL, MSG_INFO, "Reading preferences override file"); fclose(f); global_prefs.override_file_present = true; } } msg_printf(NULL, MSG_INFO, "Preferences:"); msg_printf(NULL, MSG_INFO, " max memory usage when active: %.2fMB", (host_info.m_nbytes*global_prefs.ram_max_used_busy_frac)/MEGA ); msg_printf(NULL, MSG_INFO, " max memory usage when idle: %.2fMB", (host_info.m_nbytes*global_prefs.ram_max_used_idle_frac)/MEGA ); #ifndef SIM get_disk_usages(); msg_printf(NULL, MSG_INFO, " max disk usage: %.2fGB", allowed_disk_usage(total_disk_usage)/GIGA ); #endif // max_cpus, bandwidth limits may have changed // set_ncpus(); if (ncpus != host_info.p_ncpus) { msg_printf(NULL, MSG_INFO, " max CPUs used: %d", ncpus ); } if (!global_prefs.run_if_user_active) { msg_printf(NULL, MSG_INFO, " don't compute while active"); #ifdef ANDROID } else { msg_printf(NULL, MSG_INFO, " Android: don't compute while active"); global_prefs.run_if_user_active = false; #endif } if (!global_prefs.run_gpu_if_user_active) { msg_printf(NULL, MSG_INFO, " don't use GPU while active"); } if (global_prefs.suspend_cpu_usage) { msg_printf(NULL, MSG_INFO, " suspend work if non-BOINC CPU load exceeds %.0f %%", global_prefs.suspend_cpu_usage ); } if (global_prefs.max_bytes_sec_down) { msg_printf(NULL, MSG_INFO, " max download rate: %.0f bytes/sec", global_prefs.max_bytes_sec_down ); } if (global_prefs.max_bytes_sec_up) { msg_printf(NULL, MSG_INFO, " max upload rate: %.0f bytes/sec", global_prefs.max_bytes_sec_up ); } #ifndef SIM file_xfers->set_bandwidth_limits(true); file_xfers->set_bandwidth_limits(false); #endif msg_printf(NULL, MSG_INFO, " (to change preferences, visit a project web site or select Preferences in the Manager)" ); request_schedule_cpus("Prefs update"); request_work_fetch("Prefs update"); #ifndef SIM active_tasks.request_reread_app_info(); #endif } int CLIENT_STATE::save_global_prefs( char* global_prefs_xml, char* master_url, char* scheduler_url ) { FILE* f = boinc_fopen(GLOBAL_PREFS_FILE_NAME, "w"); if (!f) return ERR_FOPEN; fprintf(f, "\n" ); // tag with the project and scheduler URL, // but only if not already tagged // if (!strstr(global_prefs_xml, "")) { fprintf(f, " %s\n" " %s\n", master_url, scheduler_url ); } fprintf(f, "%s" "\n", global_prefs_xml ); fclose(f); return 0; } // amount of RAM usable now // double CLIENT_STATE::available_ram() { if (user_active) { return host_info.m_nbytes * global_prefs.ram_max_used_busy_frac; } else { return host_info.m_nbytes * global_prefs.ram_max_used_idle_frac; } } // max amount that will ever be usable // double CLIENT_STATE::max_available_ram() { return host_info.m_nbytes*std::max( global_prefs.ram_max_used_busy_frac, global_prefs.ram_max_used_idle_frac ); }