// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2012 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 . // Support for plan classes defined using an XML file. // See http://boinc.berkeley.edu/trac/wiki/AppPlanSpec #include #include "util.h" #include "coproc.h" #include "sched_config.h" #include "sched_customize.h" #include "plan_class_spec.h" using std::string; // this returns a numerical OS version for Darwin/OSX and Windows, // letting us define numerical ranges for these OS versions // static double os_version_num(HOST h) { unsigned int a, b, c, d; if (strstr(h.os_name, "Darwin")) { if (sscanf(h.os_version, "%u.%u.%u", &a, &b, &c) == 3) { return 10000.0*a + 100.0*b + c; } } else if (strstr(h.os_name, "Windows")) { // example: "Enterprise Server Edition, Service Pack 1, (06.01.7601.00)" // char *p = strrchr(h.os_version,'('); if (p && (sscanf(p, "(%u.%u.%u.%u)", &a, &b, &c, &d) == 4)) { return 100000000.0*a + 1000000.0*b + 100.0*c +d; } } else if (strstr(h.os_name, "Android") || strstr(h.os_name, "Linux")) { // example: 3.0.31-g6fb96c9 // if (sscanf(h.os_version, "%u.%u.%u", &a, &b, &c) == 3) { return 10000.*a + 100.*b + c; } } // could not determine numerical OS version // return 0; } // parse "Android 4.3.1" or "Android 4.3" // static int android_version_num(HOST h) { int maj, min, rel; char* p = strstr(h.os_version, "(Android "); if (!p) return 0; p += strlen("(Android "); int n = sscanf(p, "%d.%d.%d", &maj, &min, &rel); if (n == 3) { return maj*10000 + min*100 + rel; } n = sscanf(p, "%d.%d", &maj, &min); if (n == 2) { return maj*10000 + min*100; } return 0; } int PLAN_CLASS_SPECS::parse_file(const char* path) { #ifndef _USING_FCGI_ FILE* f = fopen(path, "r"); #else FCGI_FILE *f = FCGI::fopen(path, "r"); #endif if (!f) return ERR_FOPEN; int retval = parse_specs(f); fclose(f); return retval; } bool PLAN_CLASS_SPEC::opencl_check(OPENCL_DEVICE_PROP& opencl_prop) { if (min_opencl_version && opencl_prop.opencl_device_version_int && min_opencl_version > opencl_prop.opencl_device_version_int ) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] OpenCL device version required min: %d, supplied: %d\n", min_opencl_version, opencl_prop.opencl_device_version_int ); } return false; } if (max_opencl_version && opencl_prop.opencl_device_version_int && max_opencl_version < opencl_prop.opencl_device_version_int ) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] OpenCL device version required max: %d, supplied: %d\n", max_opencl_version, opencl_prop.opencl_device_version_int ); } return false; } if (min_opencl_driver_revision && opencl_prop.opencl_device_version_int && min_opencl_driver_revision > opencl_prop.opencl_driver_revision ) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] OpenCL driver revision required min: %d, supplied: %d\n", min_opencl_driver_revision, opencl_prop.opencl_driver_revision ); } return false; } if (max_opencl_driver_revision && opencl_prop.opencl_device_version_int && max_opencl_driver_revision < opencl_prop.opencl_driver_revision ) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] OpenCL driver revision required max: %d, supplied: %d\n", max_opencl_driver_revision, opencl_prop.opencl_driver_revision ); } return false; } if (double_precision_fp && (opencl_prop.double_fp_config == 0)) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] device (or driver) doesn't support double precision fp math\n" ); } return false; } return true; } bool PLAN_CLASS_SPEC::check(SCHEDULER_REQUEST& sreq, HOST_USAGE& hu) { COPROC* cpp = NULL; bool can_use_multicore = true; hu.sequential_app(sreq.host.p_fpops); // CPU features // // older clients report CPU features in p_model, // within square brackets // // the requested features are surrounded by spaces, // so we can look for them with strstr() // if (!cpu_features.empty()) { char buf[8192], buf2[512]; sprintf(buf, " %s ", sreq.host.p_features); char* p = strrchr(sreq.host.p_model, '['); if (p) { sprintf(buf2, " %s", p+1); p = strchr(buf2, ']'); if (p) { *p = 0; } strcat(buf2, " "); strcat(buf, buf2); } downcase_string(buf); for (unsigned int i=0; ieffective_ncpus < min_ncpus) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: not enough CPUs: %d < %f\n", g_wreq->effective_ncpus, min_ncpus ); } return false; } // host summary // if (have_host_summary_regex && regexec(&(host_summary_regex), g_reply->host.serialnum, 0, NULL, 0) ) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: host summary '%s' didn't match regexp\n", g_reply->host.serialnum ); } return false; } // OS version // if (have_os_regex && regexec(&(os_regex), sreq.host.os_version, 0, NULL, 0)) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: OS version '%s' didn't match regexp\n", sreq.host.os_version ); } return false; } if (min_os_version || max_os_version) { double host_os_version_num = os_version_num(sreq.host); if (!host_os_version_num) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: Can't determine numerical OS version '%s'\n", sreq.host.os_version ); } return false; } if (min_os_version && (host_os_version_num < min_os_version)) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: OS version '%s' too low (%.0f / %.0f)\n", sreq.host.os_version, host_os_version_num, min_os_version ); } return false; } if (max_os_version && (host_os_version_num > max_os_version)) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: OS version '%s' too high (%.0f / %.0f)\n", sreq.host.os_version, host_os_version_num, max_os_version ); } return false; } } if (min_android_version || max_android_version) { if (strcasecmp(sreq.host.os_name, "android")) return false; int host_android_version = android_version_num(sreq.host); if (!host_android_version) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: Can't determine numerical Android version '%s'\n", sreq.host.os_version ); } if (min_android_version>0) return false; } if (min_android_version && (host_android_version < min_android_version)) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: Android version '%s' too low (%d / %d)\n", sreq.host.os_version, host_android_version, min_android_version ); } return false; } if (max_android_version && (host_android_version > max_android_version)) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: Android version '%s' too high (%d / %d)\n", sreq.host.os_version, host_android_version, max_android_version ); } return false; } } // CPU vendor // if (have_cpu_vendor_regex && regexec(&(cpu_vendor_regex), sreq.host.p_vendor, 0, NULL, 0)) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: CPU vendor '%s' didn't match regexp\n", sreq.host.p_vendor ); } return false; } // BOINC versions // if (min_core_client_version && sreq.core_client_version < min_core_client_version) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: Need newer BOINC core client: %d < %d\n", sreq.core_client_version, min_core_client_version ); } add_no_work_message("A newer BOINC may be required for some tasks."); return false; } if (max_core_client_version && sreq.core_client_version > max_core_client_version) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: Need older BOINC core client: %d > %d\n", sreq.core_client_version, max_core_client_version ); } return false; } if (virtualbox) { // host must run 7.0+ client // if (sreq.core_client_major_version < 7) { add_no_work_message("BOINC client 7.0+ required for Virtualbox jobs"); return false; } // host must have VirtualBox 3.2 or later // if (strlen(sreq.host.virtualbox_version) == 0) { add_no_work_message("VirtualBox is not installed"); return false; } int n, maj, min, rel; n = sscanf(sreq.host.virtualbox_version, "%d.%d.%d", &maj, &min, &rel); if (n != 3) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: can't parse vbox version\n" ); } return false; } int v = maj*10000 + min*100 + rel; if (min_vbox_version && v < min_vbox_version) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: vbox version too low: %d < %d\n", v, min_vbox_version ); } return false; } if (max_vbox_version && v > max_vbox_version) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: vbox version too high: %d > %d\n", v, max_vbox_version ); } return false; } if (in_vector(v, exclude_vbox_version)) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: vbox version %d excluded\n", v ); } return false; } if (vm_accel_required) { if ((!strstr(sreq.host.p_features, "vmx") && !strstr(sreq.host.p_features, "svm")) || sreq.host.p_vm_extensions_disabled ) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: missing VM HW acceleration\n" ); } return false; } } // host must have VM acceleration in order to run multi-core jobs // if (max_threads > 1) { if ((!strstr(sreq.host.p_features, "vmx") && !strstr(sreq.host.p_features, "svm")) || sreq.host.p_vm_extensions_disabled ) { can_use_multicore = false; } } // only send the version for host's primary platform. // A Win64 host can't run a 32-bit VM app: // it will look in the 32-bit half of the registry and fail // PLATFORM* p = g_request->platforms.list[0]; if (is_64b_platform(p->name)) { if (!is64bit) return false; } else { if (is64bit) return false; } } // project-specific preference // if (have_project_prefs_regex && strlen(project_prefs_tag)) { char tag[256], value[256]; char buf[65536]; extract_venue(g_reply->user.project_prefs, g_reply->host.venue, buf, sizeof(buf)); sprintf(tag,"<%s>",project_prefs_tag); bool p = parse_str(buf, tag, value, sizeof(value)); if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: parsed project prefs setting '%s' : %s\n", project_prefs_tag, p?"true":"false" ); } if (p ? regexec(&(project_prefs_regex), value, 0, NULL, 0) : !project_prefs_default_true) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: project prefs setting '%s' value='%s' prevents using plan class.\n", project_prefs_tag, p ? value : "(tag missing)" ); } return false; } } double gpu_ram = 0; int driver_version = 0; double gpu_utilization = 1.0; // user defined gpu_utilization // if (strlen(gpu_utilization_tag)) { char tag[256]; char buf[65536]; double v = 0; extract_venue(g_reply->user.project_prefs, g_reply->host.venue, buf, sizeof(buf)); sprintf(tag,"<%s>",gpu_utilization_tag); bool p = parse_double(buf, tag, v); if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: parsed project prefs setting '%s' : %s : %f\n", gpu_utilization_tag, p?"true":"false", v ); } if (v) { gpu_utilization = v; } } // AMD // if (!strcmp(gpu_type, "amd") || !strcmp(gpu_type, "ati")) { COPROC_ATI& cp = sreq.coprocs.ati; cpp = &cp; if (!cp.count) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: No AMD GPUs found\n" ); } return false; } if (min_gpu_ram_mb) { gpu_requirements[PROC_TYPE_AMD_GPU].update(0, min_gpu_ram_mb * MEGA); } if (min_driver_version) { gpu_requirements[PROC_TYPE_AMD_GPU].update(abs(min_driver_version), 0); } if (need_ati_libs) { if (!cp.atirt_detected) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: ATI libraries not found\n" ); } return false; } } else { if (need_amd_libs && !cp.amdrt_detected) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: AMD libraries not found\n" ); } return false; } } if (without_opencl) { if (cp.have_opencl) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: OpenCL detected. Plan restricted to CAL only GPUs\n" ); } return false; } } if (min_cal_target && cp.attribs.target < min_cal_target) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: CAL target less than minimum (%d < %d)\n", cp.attribs.target, min_cal_target ); } return false; } if (max_cal_target && cp.attribs.target > max_cal_target) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: CAL target greater than maximum (%d > %d)\n", cp.attribs.target, max_cal_target ); } return false; } cp.set_peak_flops(); gpu_ram = cp.opencl_prop.global_mem_size; driver_version = 0; if (cp.have_cal) { int major, minor, release, scanned; scanned = sscanf(cp.version, "%d.%d.%d", &major, &minor, &release); if (scanned != 3) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: driver version '%s' couldn't be parsed\n", cp.version ); } return false; } else { driver_version = ati_version_int(major,minor,release); } } else { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: no CAL, driver version couldn't be determined\n" ); } } // NVIDIA // } else if (!strcmp(gpu_type, "nvidia")) { COPROC_NVIDIA& cp = sreq.coprocs.nvidia; cpp = &cp; if (!cp.count) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: No NVIDIA GPUs found\n" ); } return false; } // in analogy to ATI/AMD driver_version=cp.display_driver_version; if (min_gpu_ram_mb) { gpu_requirements[PROC_TYPE_NVIDIA_GPU].update(0, min_gpu_ram_mb * MEGA); } if (min_driver_version) { gpu_requirements[PROC_TYPE_NVIDIA_GPU].update(abs(min_driver_version), 0); } // compute capability int v = (cp.prop.major)*100 + cp.prop.minor; if (min_nvidia_compcap && min_nvidia_compcap > v) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: NVIDIA compute capability required min: %d, supplied: %d\n", min_nvidia_compcap, v ); } return false; } if (max_nvidia_compcap && max_nvidia_compcap < v) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: CUDA compute capability required max: %d, supplied: %d\n", max_nvidia_compcap, v ); } return false; } if (cuda) { // CUDA version if (min_cuda_version && min_cuda_version > cp.cuda_version) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: CUDA version required min: %d, supplied: %d\n", min_cuda_version, cp.cuda_version ); } return false; } if (max_cuda_version && max_cuda_version < cp.cuda_version) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: CUDA version required max: %d, supplied: %d\n", max_cuda_version, cp.cuda_version ); } return false; } } gpu_ram = cp.prop.totalGlobalMem; cp.set_peak_flops(); // Intel GPU // } else if (strstr(gpu_type, "intel") == gpu_type) { COPROC& cp = sreq.coprocs.intel_gpu; cpp = &cp; if (!cp.count) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: No Intel GPUs found\n" ); } return false; } if (min_gpu_ram_mb) { gpu_requirements[PROC_TYPE_INTEL_GPU].update(0, min_gpu_ram_mb * MEGA); } // custom GPU type // } else if (strlen(gpu_type)) { cpp = sreq.coprocs.lookup_type(gpu_type); if (!cpp) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: No %s found\n", gpu_type ); } return false; } if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: Custom coproc %s found\n", gpu_type ); } } if (opencl) { if (cpp) { if (!cpp->have_opencl) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] GPU doesn't support OpenCL\n" ); } return false; } if (!opencl_check(cpp->opencl_prop)) { return false; } gpu_ram = cpp->opencl_prop.global_mem_size; } else { // OpenCL CPU app version. // The host may have several OpenCL CPU libraries. // See if any of them works. // TODO: there should be a way of saying which library // the app version requires, // or a way of conveying to the app which one to use. // bool found = false; for (int i=0; i gpu_ram) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: GPU RAM required min: %f, supplied: %f\n", min_gpu_ram_mb * MEGA, gpu_ram ); } return false; } // (display) driver version if (min_driver_version && driver_version) { if (min_driver_version > driver_version) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: driver version required min: %d, supplied: %d\n", abs(min_driver_version), driver_version ); } return false; } } if (max_driver_version && driver_version) { if (max_driver_version < driver_version) { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: driver version required max: %d, supplied: %d\n", abs(max_driver_version), driver_version ); } return false; } } hu.gpu_ram = (gpu_ram_used_mb?gpu_ram_used_mb:min_gpu_ram_mb) * MEGA; double gpu_usage; // if ngpus < 0, set gpu_usage by the fraction of the total // video RAM a tasks would take // i.e. fill the device memory with tasks // if (ngpus < 0) { gpu_usage = (floor(gpu_ram/ hu.gpu_ram) * hu.gpu_ram) / gpu_ram ; } else if (ngpus > 0) { gpu_usage = ngpus * gpu_utilization; } else { gpu_usage = gpu_utilization; } // if we don't know GPU peak flops, treat it like a CPU app // if (cpp->peak_flops == 0) { strcpy(hu.custom_coproc_type, gpu_type); hu.avg_ncpus = cpu_frac; hu.gpu_usage = gpu_usage; } else { coproc_perf( capped_host_fpops(), gpu_peak_flops_scale * gpu_usage * cpp->peak_flops, cpu_frac, hu.projected_flops, hu.avg_ncpus ); if (avg_ncpus) { hu.avg_ncpus = avg_ncpus; } // I believe the first term here is just hu.projected_flops, // but I'm leaving it spelled out to match GPU scheduling // code in sched_customize.cpp // hu.peak_flops = gpu_peak_flops_scale*gpu_usage*cpp->peak_flops + hu.avg_ncpus*capped_host_fpops(); } if (!strcmp(gpu_type, "amd") || !strcmp(gpu_type, "ati")) { hu.proc_type = PROC_TYPE_AMD_GPU; hu.gpu_usage = gpu_usage; } else if (!strcmp(gpu_type, "nvidia")) { hu.proc_type = PROC_TYPE_NVIDIA_GPU; hu.gpu_usage = gpu_usage; } else if (strstr(gpu_type, "intel")==gpu_type) { hu.proc_type = PROC_TYPE_INTEL_GPU; hu.gpu_usage = gpu_usage; } else if (!strcmp(gpu_type, "miner_asic")) { hu.proc_type = PROC_TYPE_MINER_ASIC; hu.gpu_usage = gpu_usage; } else { if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: unknown GPU supplied: %s\n", gpu_type ); } } // CPU only // } else { if (avg_ncpus) { hu.avg_ncpus = avg_ncpus; } else { if (can_use_multicore) { if (max_threads > g_wreq->effective_ncpus) { hu.avg_ncpus = g_wreq->effective_ncpus; } else { hu.avg_ncpus = max_threads; } } else { hu.avg_ncpus = 1; } } if (nthreads_cmdline) { sprintf(hu.cmdline, "--nthreads %d", (int)hu.avg_ncpus); } hu.peak_flops = capped_host_fpops() * hu.avg_ncpus; hu.projected_flops = capped_host_fpops() * hu.avg_ncpus * projected_flops_scale; } hu.max_ncpus = hu.avg_ncpus; #if 0 if (config.debug_version_select) { log_messages.printf(MSG_NORMAL, "[version] plan_class_spec: host_flops: %e, \tscale: %.2f, \tprojected_flops: %e, \tpeak_flops: %e\n", sreq.host.p_fpops, projected_flops_scale, hu.projected_flops, hu.peak_flops ); } #endif return true; } bool PLAN_CLASS_SPECS::check( SCHEDULER_REQUEST& sreq, char* plan_class, HOST_USAGE& hu ) { for (unsigned int i=0; ieffective_ncpus = 4; if (1) { sreq.coprocs.nvidia.fake(18000, 512*MEGA, 490*MEGA, 1); sreq.coprocs.nvidia.opencl_prop.opencl_device_version_int = 0; } else { sreq.coprocs.nvidia.count = 0; } if (1) { sreq.coprocs.ati.fake(512*MEGA, 256*MEGA, 1); sreq.coprocs.ati.have_cal = true; sreq.coprocs.ati.opencl_prop.opencl_device_version_int = 0; } else { sreq.coprocs.ati.count = 0; } for (unsigned int i=0; i