diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..352e960 --- /dev/null +++ b/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..2f82e76 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + SheepitClient + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..c97c55c --- /dev/null +++ b/build.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extern/args4j.jar b/extern/args4j.jar new file mode 100644 index 0000000..e188b3d Binary files /dev/null and b/extern/args4j.jar differ diff --git a/extern/commons-compress.jar b/extern/commons-compress.jar new file mode 100644 index 0000000..51baf91 Binary files /dev/null and b/extern/commons-compress.jar differ diff --git a/extern/jna.jar b/extern/jna.jar new file mode 100644 index 0000000..9038048 Binary files /dev/null and b/extern/jna.jar differ diff --git a/extern/platform.jar b/extern/platform.jar new file mode 100644 index 0000000..4b3d567 Binary files /dev/null and b/extern/platform.jar differ diff --git a/protocol.txt b/protocol.txt new file mode 100644 index 0000000..e94d042 --- /dev/null +++ b/protocol.txt @@ -0,0 +1,172 @@ +=== Session creation === + +Url: Root server url (not including /server) +Parameters as GET: + * login: User's login in plain text. + * password: User's password in plain text. + * version: Client's version, for example 3.3.1762. + * os: Computer's operating system, at the moment only "windows", "mac" and "linux" are supported. + * bits: Architecture size, at the moment only "32bit" and "64bit" are supported. + * cpu_family: CPU's family, on linux it can be get in /proc/cpuinfo via the attribute "cpu fanily". + * cpu_model: CPU's model, on linux it can be get in /proc/cpuinfo via the attribute "model". + * cpu_model_name: CPU's model as human readable, on linux it can be get in /proc/cpuinfo via the attribute "model name". + * cpu_cores: Number of core (or thread) available. + * ram: Memory available in bytes. + * extras (optional): Extra data use for the configuration. + +Answer in case of error: + + +where X: + * 100 => Error no version given. + * 101 => Client is too old. + * 102 => Authentification failure. + * 103 => WebSession have expirated. + * 104 => Missing parameter. + * something else => Unknown error. + +Answer with no error: +The status="0" to specify everything is okay, plus a list of destination for request, validation job, etc. +It provide a destination for error, job request, job validation, download needed file, heart beat (keepmealive) and logout. +The maximum duration between two heart beat is given by the attribute "max-period" who is in second. + + + + + + + + + + + +=== Session end === + +Url: use the request type "logout" from the configuration answer. +No additional parameter is required. + +=== Download renderer archive === + +Url: use the request type "download-archive" from the configuration answer. +Parameter as GET or POST: + * type: "binary" + * job: id of the job + +Answer: +No error: the file +On error: an 404 http code + + +=== Download job archive === + +Url: use the request type "download-archive" from the configuration answer. +Parameter as GET or POST: + * type: "job" + * job: Id of the job + * revision: Revision of the job + +Answer: +No error: the file +On error: an 404 http code + + +=== Job request === + +Url: use the request type "request-job" from the configuration answer. +Parameter as GET or POST: + * computemethod: What are my computer capability, 0 for using gpu and cpu (the computer is capable of using both but it will not use it at the same time), 1 for cpu only, 2 for gpu only. + +Answer in case of error: + + +where X: + * 0 => No error + * 200 => No job available, the client should wait few minutes before requesting a new job (typical value is 1hour). + * 201 => The client does not have rendering right. + * 202 => Client's session is dead. Client should do a config request before requesting a new job. + * 203 => Client's session have been disabled (usually because the client is sending broken frame). The client warms the end user and logout. + * something else => unknown error + +Answer with no error: + + + + + + + + + + + +=== Job validation === + +Url: use the request type "validate-job" from the configuration answer. +Parameter as GET or POST: + * job: Job's id + * frame: Job's frame number + * rendertime: Job's render time + * revision: Job's revision + * extras: Job's extra data + * file: Frame to validate as form-data post + * memoryused (optional): Max memory used for the render + * cores (optional): Number of cores used from the render +Parameter as form-urlencoded: + * file: the frame to send + +Answer in case of error: + +where X: + * 0 => No error + * 300 => Missing parameter in request. + * 301 => Client generated a broken frame (usually an too old gpu who generated black frame). + * 302 => Client sent a file who is not an image. + * 303 => Failed to upload the image to the server. + * 304 => Client's session is disabled or dead. + * something else => unknown error + +=== Session heart bit === + +Url: use the request type "keepmealive" from the configuration answer. +Parameter as GET or POST: + * job: Id of the rendering job + * frame: Frame number of the rendering job + * extras: Extras data get on job request of the rendering job + +Answer: + + +where X: + * 0 => No error. + * 400 => Stop this job (usually because the job has be cancelled on the server side). + +=== Error send === + +Url: use the request type "error" from the configuration answer. +Parameter as GET or POST: + * job: job's id + * frame: job's frame number + * extras: job's extra data + * rendertime (optional): job's frame number + * memoryused (optional): max memory used for the render +Parameter as form-urlencoded: + * file: the error log to send + + +Answer: + + +where X: + * 0 => No error + diff --git a/src/com/sheepit/client/Client.java b/src/com/sheepit/client/Client.java new file mode 100644 index 0000000..2d8864e --- /dev/null +++ b/src/com/sheepit/client/Client.java @@ -0,0 +1,890 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import com.sheepit.client.Error.ServerCode; +import com.sheepit.client.Error.Type; +import com.sheepit.client.exception.FermeException; +import com.sheepit.client.exception.FermeExceptionNoRightToRender; +import com.sheepit.client.exception.FermeExceptionNoSession; +import com.sheepit.client.exception.FermeExceptionSessionDisabled; +import com.sheepit.client.os.OS; + +public class Client { + public static final String UPDATE_METHOD_BY_LINE_NUMBER = "linenumber"; + public static final String UPDATE_METHOD_BY_REMAINING_TIME = "remainingtime"; + + private Gui gui; + private Server server; + private Configuration config; + private Log log; + private Job renderingJob; + private BlockingQueue jobsToValidate; + private boolean isValidatingJob; + + private boolean disableErrorSending; + private boolean running; + + public Client(Gui gui_, Configuration config, String url_) { + this.config = config; + this.server = new Server(url_, this.config, this); + this.log = Log.getInstance(this.config); + this.gui = gui_; + this.renderingJob = null; + this.jobsToValidate = new ArrayBlockingQueue(1024); + this.isValidatingJob = false; + + this.disableErrorSending = false; + this.running = true; + } + + public String toString() { + return String.format("Client (config %s, server %s)", this.config, this.server); + } + + public Job getRenderingJob() { + return this.renderingJob; + } + + public Gui getGui() { + return this.gui; + } + + public Configuration getConfiguration() { + return this.config; + } + + public int run() { + int step; + try { + step = this.log.newCheckPoint(); + this.gui.status("Starting"); + + this.config.cleanWorkingDirectory(); + + Error.Type ret; + ret = this.server.getConfiguration(); + + if (ret != Error.Type.OK) { + this.gui.error(Error.humainString(ret)); + if (ret != Error.Type.AUTHENTICATION_FAILED) { + Log.printCheckPoint(step); + } + return -1; + } + + this.server.start(); // for staying alive + + // create a thread who will send the frame + Runnable runnable_sender = new Runnable() { + public void run() { + senderLoop(); + } + }; + Thread thread_sender = new Thread(runnable_sender); + thread_sender.start(); + + this.renderingJob = null; + + while (this.running == true) { + step = this.log.newCheckPoint(); + try { + Calendar next_request = this.nextJobRequest(); + if (next_request != null) { + // wait + Date now = new Date(); + this.gui.status(String.format("Waiting until %tR before requesting job", next_request)); + try { + Thread.sleep(next_request.getTimeInMillis() - now.getTime()); + } + catch (InterruptedException e3) { + + } + catch (IllegalArgumentException e3) { + this.log.error("Client::run sleepA failed " + e3); + } + } + this.gui.status("Requesting Job"); + this.renderingJob = this.server.requestJob(); + } + catch (FermeExceptionNoRightToRender e) { + this.gui.error("User does not enough right to render scene"); + return -2; + } + catch (FermeExceptionSessionDisabled e) { + this.gui.error(Error.humainString(Error.Type.SESSION_DISABLED)); + // should wait forever to actually display the message to the user + while (true) { + try { + Thread.sleep(100000); + } + catch (InterruptedException e1) { + } + } + } + catch (FermeExceptionNoSession e) { + // User have no session need to re-authenticate + + ret = this.server.getConfiguration(); + if (ret != Error.Type.OK) { + this.renderingJob = null; + } + else { + try { + Calendar next_request = this.nextJobRequest(); + if (next_request != null) { + // wait + Date now = new Date(); + this.gui.status(String.format("Waiting until %tR before requesting job", next_request)); + try { + Thread.sleep(next_request.getTimeInMillis() - now.getTime()); + } + catch (InterruptedException e3) { + + } + catch (IllegalArgumentException e3) { + this.log.error("Client::run sleepB failed " + e3); + } + } + this.gui.status("Requesting Job"); + this.renderingJob = this.server.requestJob(); + } + catch (FermeException e1) { + this.renderingJob = null; + } + } + } + catch (FermeException e) { + this.gui.error("Client::renderingManagement exception requestJob (1) " + e.getMessage()); + this.sendError(step); + continue; + } + + if (this.renderingJob == null) { // no job + int time_sleep = 1000 * 60 * 15; + Date wakeup_time = new Date(new Date().getTime() + time_sleep); + this.gui.status(String.format("No job available. Sleeping for 15 minutes (will wake up at ~%tR)", wakeup_time)); + this.gui.framesRemaining(0); + int time_slept = 0; + while (time_slept < time_sleep && this.running == true) { + try { + Thread.sleep(5000); + } + catch (InterruptedException e) { + return -3; + } + time_slept += 5000; + } + continue; // go back to ask job + } + + this.log.debug("Got work to do id: " + this.renderingJob.getId() + " frame: " + this.renderingJob.getFrameNumber()); + + ret = this.work(this.renderingJob); + if (ret != Error.Type.OK) { + Job frame_to_reset = this.renderingJob; // copie it because the sendError will take ~5min to execute + this.renderingJob = null; + this.gui.error(Error.humainString(ret)); + this.sendError(step, frame_to_reset, ret); + this.log.removeCheckPoint(step); + continue; + } + + if (this.renderingJob.simultaneousUploadIsAllowed() == false) { // power or compute_method job, need to upload right away + ret = confirmJob(this.renderingJob); + if (ret != Error.Type.OK) { + gui.error("Client::renderingManagement problem with confirmJob (returned " + ret + ")"); + sendError(step); + } + } + else { + this.jobsToValidate.add(this.renderingJob); + this.renderingJob = null; + } + + while (this.shouldWaitBeforeRender() == true) { + try { + Thread.sleep(4000); // wait a little bit + } + catch (InterruptedException e3) { + } + } + this.log.removeCheckPoint(step); + } + + // not running but maybe still sending frame + while (this.jobsToValidate.size() > 0) { + try { + Thread.sleep(2300); // wait a little bit + } + catch (InterruptedException e3) { + } + } + } + catch (Exception e1) { + // no exception should be raise to actual launcher (applet or standalone) + return -99; // the this.stop will be done after the return of this.run() + } + + return 0; + } + + public synchronized int stop() { + System.out.println("Client::stop"); + this.running = false; + this.disableErrorSending = true; + + if (this.renderingJob != null) { + if (this.renderingJob.getProcess() != null) { + OS.getOS().kill(this.renderingJob.getProcess()); + } + } + + // this.config.workingDirectory.delete(); + this.config.removeWorkingDirectory(); + + if (this.server == null) { + return 0; + } + + try { + this.server.HTTPRequest(this.server.getPage("logout")); + } + catch (IOException e) { + // nothing to do: if the logout failed that's ok + } + this.server.interrupt(); + try { + this.server.join(); + } + catch (InterruptedException e) { + } + + this.server = null; + + this.gui.stop(); + return 0; + } + + public void askForStop() { + System.out.println("Client::askForStop"); + this.running = false; + } + + public int senderLoop() { + int step = log.newCheckPoint(); + Error.Type ret; + while (true) { + Job job_to_send; + try { + job_to_send = (Job) jobsToValidate.take(); + this.log.debug("will validate " + job_to_send); + //gui.status("Sending frame"); + ret = confirmJob(job_to_send); + if (ret != Error.Type.OK) { + this.gui.error(Error.humainString(ret)); + sendError(step); + } + else { + gui.AddFrameRendered(); + } + } + catch (InterruptedException e) { + } + } + } + + protected void sendError(int step_) { + this.sendError(step_, null, null); + } + + protected void sendError(int step_, Job job_to_reset_, Error.Type error) { + if (this.disableErrorSending) { + this.log.debug("Error sending is disable, do not send log"); + return; + } + + try { + File temp_file = File.createTempFile("farm_", ""); + temp_file.createNewFile(); + temp_file.deleteOnExit(); + FileOutputStream writer = new FileOutputStream(temp_file); + + ArrayList logs = this.log.getForCheckPoint(step_); + for (String line : logs) { + writer.write(line.getBytes()); + writer.write('\n'); + } + + writer.close(); + String args = ""; + if (job_to_reset_ != null) { + args = "?frame=" + job_to_reset_.getFrameNumber() + "&job=" + job_to_reset_.getId() + "&render_time=" + job_to_reset_.getRenderDuration(); + if (job_to_reset_.getExtras() != null && job_to_reset_.getExtras().length() > 0) { + args += "&extras=" + job_to_reset_.getExtras(); + } + } + this.server.HTTPSendFile(this.server.getPage("error") + args, temp_file.getAbsolutePath()); + temp_file.delete(); + } + catch (Exception e1) { + e1.printStackTrace(); + // no exception should be raise to actual launcher (applet or standalone) + } + + if (error != null && error == Error.Type.RENDERER_CRASHED) { + // do nothing, we can ask for a job right away + } + else { + try { + Thread.sleep(300000); // sleeping for 5min + } + catch (InterruptedException e) { + } + } + } + + /** + * + * @return the date of the next request, or null is there is not delay (null <=> now) + */ + public Calendar nextJobRequest() { + if (this.config.requestTime == null) { + return null; + } + else { + Calendar next = null; + Calendar now = Calendar.getInstance(); + for (Pair interval : this.config.requestTime) { + Calendar start = (Calendar) now.clone(); + Calendar end = (Calendar) now.clone(); + start.set(Calendar.SECOND, 00); + start.set(Calendar.MINUTE, interval.first.get(Calendar.MINUTE)); + start.set(Calendar.HOUR_OF_DAY, interval.first.get(Calendar.HOUR_OF_DAY)); + + end.set(Calendar.SECOND, 59); + end.set(Calendar.MINUTE, interval.second.get(Calendar.MINUTE)); + end.set(Calendar.HOUR_OF_DAY, interval.second.get(Calendar.HOUR_OF_DAY)); + + if (start.before(now) && now.before(end)) { + return null; + } + if (start.after(now)) { + if (next == null) { + next = start; + } + else { + if (start.before(next)) { + next = start; + } + } + } + } + + return next; + } + } + + public Error.Type work(Job ajob) { + if (ajob.workeable() == false) { + this.log.error("Client::work The received job is not workeable"); + return Error.Type.WRONG_CONFIGURATION; + } + int ret; + + ret = this.downloadExecutable(ajob); + if (ret != 0) { + this.log.error("Client::work problem with downloadExecutable (ret " + ret + ")"); + return Error.Type.DOWNLOAD_FILE; + } + + ret = this.downloadSceneFile(ajob); + if (ret != 0) { + this.log.error("Client::work problem with downloadSceneFile (ret " + ret + ")"); + return Error.Type.DOWNLOAD_FILE; + } + + ret = this.prepareWorkeableDirectory(ajob); // decompress renderer and scene archives + if (ret != 0) { + this.log.error("Client::work problem with this.prepareWorkeableDirectory (ret " + ret + ")"); + return Error.Type.CAN_NOT_CREATE_DIRECTORY; + } + + File scene_file = new File(ajob.getScenePath()); + File renderer_file = new File(ajob.getRendererPath()); + + if (scene_file.exists() == false) { + this.log.error("Client::work job prepration failed (scene file '" + scene_file.getAbsolutePath() + "' does not exist)"); + return Error.Type.MISSING_SCENE; + } + + if (renderer_file.exists() == false) { + this.log.error("Client::work job prepration failed (renderer file '" + renderer_file.getAbsolutePath() + "' does not exist)"); + return Error.Type.MISSING_RENDER; + } + + Error.Type err = this.runRenderer(ajob); + if (err != Error.Type.OK) { + this.log.error("Client::work problem with runRenderer (ret " + err + ")"); + return err; + } + + return Error.Type.OK; + } + + protected Error.Type runRenderer(Job ajob) { + this.gui.status("Rendering"); + String core_script = ""; + if (ajob.getUseGPU() && this.config.getGPUDevice() != null) { + core_script += "import bpy\n" + "bpy.context.user_preferences.system.compute_device_type = \"CUDA\"" + "\n" + "bpy.context.scene.cycles.device = \"GPU\"" + "\n" + "bpy.context.user_preferences.system.compute_device = \"" + this.config.getGPUDevice().getCudaName() + "\"\n" + "bpy.context.scene.render.tile_x = 256" + "\n" + "bpy.context.scene.render.tile_y = 256" + "\n"; + } + else { + core_script += "import bpy\n" + "bpy.context.user_preferences.system.compute_device_type = \"NONE\"" + "\n" + "bpy.context.scene.cycles.device = \"CPU\"" + "\n" + "bpy.context.scene.render.tile_x = 32" + "\n" + "bpy.context.scene.render.tile_y = 32" + "\n"; + } + File script_file = null; + String command1[] = ajob.getRenderCommand().split(" "); + int size_command = command1.length + 2; // + 2 for script + + if (this.config.getNbCores() > 0) { // user have specified something + size_command += 2; + } + + String[] command = new String[size_command]; + + int index = 0; + for (int i = 0; i < command1.length; i++) { + if (command1[i].equals(".c")) { + command[index] = ajob.getScenePath(); + index += 1; + command[index] = "-P"; + index += 1; + + try { + script_file = File.createTempFile("script_", "", this.config.workingDirectory); + File file = new File(script_file.getAbsolutePath()); + FileWriter txt; + txt = new FileWriter(file); + + PrintWriter out = new PrintWriter(txt); + out.write(ajob.getScript()); + out.write("\n"); + out.write(core_script); // GPU part + out.write("\n"); // GPU part + out.close(); + + command[index] = script_file.getAbsolutePath(); + } + catch (IOException e) { + return Error.Type.UNKNOWN; + } + script_file.deleteOnExit(); + } + else if (command1[i].equals(".e")) { + command[index] = ajob.getRendererPath(); + // the number of cores have to be put after the binary and before the scene arg + if (this.config.getNbCores() > 0) { + index += 1; + command[index] = "-t"; + index += 1; + command[index] = Integer.toString(this.config.getNbCores()); + //index += 1; // do not do it, it will be done at the end of the loop + } + } + else if (command1[i].equals(".o")) { + command[index] = this.config.workingDirectory.getAbsolutePath() + File.separator + ajob.getPrefixOutputImage(); + } + else if (command1[i].equals(".f")) { + command[index] = ajob.getFrameNumber(); + } + else { + command[index] = command1[i]; + } + index += 1; + } + + long rending_start = new Date().getTime(); + + int nb_lines = 0; + try { + String line; + this.log.debug(Arrays.toString(command)); + OS os = OS.getOS(); + ajob.setProcess(os.exec(command)); + BufferedReader input = new BufferedReader(new InputStreamReader(ajob.getProcess().getInputStream())); + + long last_update_status = 0; + this.log.debug("renderer output"); + while ((line = input.readLine()) != null) { + nb_lines++; + this.updateRenderingMemoryPeak(line, ajob); + + this.log.debug(line); + if ((new Date().getTime() - last_update_status) > 2000) { // only call the update every two seconds + this.updateRenderingStatus(line, nb_lines, ajob); + last_update_status = new Date().getTime(); + } + } + input.close(); + this.log.debug("end of rendering"); + } + catch (Exception err) { + StringWriter sw = new StringWriter(); + err.printStackTrace(new PrintWriter(sw)); + this.log.error("Client:runRenderer exception(A) " + err + " stacktrace " + sw.toString()); + return Error.Type.FAILED_TO_EXECUTE; + } + + long rending_end = new Date().getTime(); + + if (script_file != null) { + script_file.delete(); + } + + ajob.setRenderDuration((int) ((rending_end - rending_start) / 1000 + 1)); // render time is in seconds but the getTime is in millisecond + + ajob.setMaxOutputNbLines(nb_lines); + int exit_value = 0; + try { + exit_value = ajob.getProcess().exitValue(); + } + catch (IllegalThreadStateException e) { + // the process is not finished yet + exit_value = 0; + } + catch (Exception e) { + // actually is for java.io.IOException: GetExitCodeProcess error=6, The handle is invalid + // it was not declared throwable + + // the process is not finished yet + exit_value = 0; + } + + ajob.setProcess(null); + + // find the picture file + final String namefile_without_extension = ajob.getPrefixOutputImage() + ajob.getFrameNumber(); + + FilenameFilter textFilter = new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.startsWith(namefile_without_extension); + } + }; + + File[] files = this.config.workingDirectory.listFiles(textFilter); + + if (files.length == 0) { + this.log.error("Client::runRenderer no picture file found (after finished render (namefile_without_extension " + namefile_without_extension + ")"); + + String basename = ""; + try { + basename = ajob.getPath().substring(0, ajob.getPath().lastIndexOf('.')); + } + catch (Exception e) { + e.printStackTrace(); + } + File crash_file = new File(this.config.workingDirectory + File.separator + basename + ".crash.txt"); + if (crash_file.exists()) { + this.log.error("Client::runRenderer crash file found => the renderer crashed"); + crash_file.delete(); + return Error.Type.RENDERER_CRASHED; + } + + if (exit_value == 127 && ajob.getRenderDuration() < 10) { + this.log.error("Client::runRenderer renderer return 127 and render time took " + ajob.getRenderDuration() + "s, mostly missing libraries"); + return Error.Type.RENDERER_MISSING_LIBRARIES; + } + + return Error.Type.NOOUTPUTFILE; + } + else { + ajob.setOutputImagePath(files[0].getAbsolutePath()); + this.log.debug("Client::runRenderer pictureFilename: '" + ajob.getOutputImagePath() + "'"); + } + + File scene_dir = new File(ajob.getSceneDirectory()); + long date_modification_scene_directory = (long) Utils.lastModificationTime(scene_dir); + if (date_modification_scene_directory > rending_start) { + scene_dir.delete(); + } + + this.gui.status(String.format("Frame rendered in %dmin%ds", ajob.getRenderDuration() / 60, ajob.getRenderDuration() % 60)); + + return Error.Type.OK; + } + + protected int downloadSceneFile(Job ajob_) { + this.gui.status("Downloading scene"); + + String achive_local_path = ajob_.getSceneArchivePath(); + + File renderer_achive_local_path_file = new File(achive_local_path); + + if (renderer_achive_local_path_file.exists()) { + // the archive have been already downloaded + } + else { + // we must download the archive + int ret; + String real_url; + real_url = String.format("%s?type=job&job=%s&revision=%s", this.server.getPage("download-archive"), ajob_.getId(), ajob_.getRevision()); + ret = this.server.HTTPGetFile(real_url, achive_local_path, this.gui, "Downloading scene %s %%"); + if (ret != 0) { + this.gui.error("Client::downloadSceneFile problem with Utils.DownloadFile returned " + ret); + return -1; + } + + String md5_local; + md5_local = Utils.md5(achive_local_path); + + if (md5_local.equals(ajob_.getSceneMD5()) == false) { + System.err.println("md5 of the downloaded file and the local file are not the same (local '" + md5_local + "' scene: '" + ajob_.getSceneMD5() + "')"); + this.log.error("Client::downloadSceneFile mismatch on md5 local: '" + md5_local + "' server: '" + ajob_.getSceneMD5() + "'"); + // md5 of the file downloaded and the file excepted is not the same + return -2; + } + } + return 0; + } + + protected int downloadExecutable(Job ajob) { + this.gui.status("Downloading renderer"); + String real_url = new String(); + real_url = String.format("%s?type=binary&job=%s", this.server.getPage("download-archive"), ajob.getId()); + + // we have the MD5 of the renderer archive + String renderer_achive_local_path = ajob.getRendererArchivePath(); + File renderer_achive_local_path_file = new File(renderer_achive_local_path); + + if (renderer_achive_local_path_file.exists()) { + // the archive have been already downloaded + } + else { + // we must download the archive + int ret; + ret = this.server.HTTPGetFile(real_url, renderer_achive_local_path, this.gui, "Downloading renderer %s %%"); + if (ret != 0) { + this.gui.error("Client::downloadExecutable problem with Utils.DownloadFile returned " + ret); + return -9; + } + } + + String md5_local; + md5_local = Utils.md5(renderer_achive_local_path); + + if (md5_local.equals(ajob.getRenderMd5()) == false) { + this.log.error("Client::downloadExecutable mismatch on md5 local: '" + md5_local + "' server: '" + ajob.getRenderMd5() + "'"); + // md5 of the file downloaded and the file excepted is not the same + return -10; + } + return 0; + } + + protected int prepareWorkeableDirectory(Job ajob) { + int ret; + String renderer_archive = ajob.getRendererArchivePath(); + String renderer_path = ajob.getRendererDirectory(); + File renderer_path_file = new File(renderer_path); + + if (renderer_path_file.exists()) { + // Directory already exists -> do nothing + } + else { + // we create the directory + renderer_path_file.mkdir(); + + // unzip the archive + ret = Utils.unzipFileIntoDirectory(renderer_archive, renderer_path); + if (ret != 0) { + this.gui.error("Client::prepareWorkeableDirectory, error with Utils.unzipFileIntoDirectory of the renderer (returned " + ret + ")"); + return -1; + } + } + + String scene_archive = ajob.getSceneArchivePath(); + String scene_path = ajob.getSceneDirectory(); + File scene_path_file = new File(scene_path); + + if (scene_path_file.exists()) { + // Directory already exists -> do nothing + } + else { + // we create the directory + scene_path_file.mkdir(); + + // unzip the archive + ret = Utils.unzipFileIntoDirectory(scene_archive, scene_path); + if (ret != 0) { + this.gui.error("Client::prepareWorkeableDirectory, error with Utils.unzipFileIntoDirectory of the scene (returned " + ret + ")"); + return -2; + } + } + + return 0; + } + + protected Error.Type confirmJob(Job ajob) { + String extras_config = ""; + if (this.config.getNbCores() > 0) { + extras_config = "&cores=" + this.config.getNbCores(); + } + + String url_real = String.format("%s?job=%s&frame=%s&rendertime=%d&revision=%s&memoryused=%s&extras=%s%s", this.server.getPage("validate-job"), ajob.getId(), ajob.getFrameNumber(), ajob.getRenderDuration(), ajob.getRevision(), ajob.getMemoryUsed(), ajob.getExtras(), extras_config); + + this.isValidatingJob = true; + int nb_try = 1; + int max_try = 3; + ServerCode ret = ServerCode.UNKNOWN; + while (nb_try < max_try && ret != ServerCode.OK) { + ret = this.server.HTTPSendFile(url_real, ajob.getOutputImagePath()); + switch (ret) { + case OK: + // no issue, exit the loop + nb_try = max_try; + break; + + case JOB_VALIDATION_ERROR_SESSION_DISABLED: + case JOB_VALIDATION_ERROR_BROKEN_MACHINE: + return Type.SESSION_DISABLED; + + case JOB_VALIDATION_ERROR_MISSING_PARAMETER: + // no point to retry the request + return Error.Type.UNKNOWN; + + default: + // do nothing, try to do a request on the next loop + break; + } + + nb_try++; + if (ret != ServerCode.OK && nb_try < max_try) { + try { + this.log.debug("Sleep for 32s before trying to re-upload the frame"); + Thread.sleep(32000); + } + catch (InterruptedException e) { + return Error.Type.UNKNOWN; + } + } + } + this.isValidatingJob = false; + + // we can remove the frame file + File frame = new File(ajob.getOutputImagePath()); + frame.delete(); + ajob.setOutputImagePath(null); + + this.isValidatingJob = false; + return Error.Type.OK; + } + + protected boolean shouldWaitBeforeRender() { + int concurrent_job = this.jobsToValidate.size(); + if (this.isValidatingJob) { + concurrent_job++; + } + return (concurrent_job >= this.config.maxUploadingJob()); + } + + protected void updateRenderingStatus(String line, int current_number_of_lines, Job ajob) { + if (ajob.getUpdateRenderingStatusMethod() != null && ajob.getUpdateRenderingStatusMethod().equals(Client.UPDATE_METHOD_BY_LINE_NUMBER) && ajob.getMaxOutputNbLines() > 0) { + this.gui.status(String.format("Rendering %s %%", (int) (100.0 * current_number_of_lines / ajob.getMaxOutputNbLines()))); + } + else if (ajob.getUpdateRenderingStatusMethod() == null || ajob.getUpdateRenderingStatusMethod().equals(Client.UPDATE_METHOD_BY_REMAINING_TIME)) { + String search_remaining = "remaining:"; + int index = line.toLowerCase().indexOf(search_remaining); + if (index != -1) { + String buf1 = line.substring(index + search_remaining.length()); + index = buf1.indexOf(" "); + + if (index != -1) { + String remaining_time = buf1.substring(0, index).trim(); + int last_index = remaining_time.lastIndexOf('.'); //format 00:00:00.00 (hr:min:sec) + if (last_index > 0) { + remaining_time = remaining_time.substring(0, last_index); + } + + try { + DateFormat date_parse_minute = new SimpleDateFormat("m:s"); + DateFormat date_parse_hour = new SimpleDateFormat("h:m:s"); + SimpleDateFormat date_output_minute = new SimpleDateFormat("mm'min'ss"); + SimpleDateFormat date_output_hour = new SimpleDateFormat("HH'h'mm'min'ss"); + DateFormat date_parse = date_parse_minute; + DateFormat date_output = date_output_minute; + if (remaining_time.split(":").length > 2) { + date_parse = date_parse_hour; + date_output = date_output_hour; + } + Date d1 = date_parse.parse(remaining_time); + this.gui.status(String.format("Rendering (remaining %s)", date_output.format(d1))); + } + catch (ParseException err) { + this.log.error("Client::updateRenderingStatus ParseException " + err); + } + } + } + } + } + + protected void updateRenderingMemoryPeak(String line, Job ajob) { + String[] elements = line.toLowerCase().split("(peak)"); + + for (String element : elements) { + if (element.isEmpty() == false && element.charAt(0) == ' ') { + int end = element.indexOf(')'); + if (end > 0) { + long mem = Utils.parseNumber(element.substring(1, end).trim()); + if (mem > ajob.getMemoryUsed()) { + ajob.setMemoryUsed(mem); + } + } + } + else { + if (element.isEmpty() == false && element.charAt(0) == ':') { + int end = element.indexOf('|'); + if (end > 0) { + long mem = Utils.parseNumber(element.substring(1, end).trim()); + if (mem > ajob.getMemoryUsed()) { + ajob.setMemoryUsed(mem); + } + } + } + } + } + } +} diff --git a/src/com/sheepit/client/Configuration.java b/src/com/sheepit/client/Configuration.java new file mode 100644 index 0000000..90c7287 --- /dev/null +++ b/src/com/sheepit/client/Configuration.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.Calendar; +import java.util.LinkedList; +import java.util.List; + +import com.sheepit.client.hardware.gpu.GPUDevice; + +public class Configuration { + public enum ComputeType { + CPU_GPU, CPU_ONLY, GPU_ONLY + }; // acccept job for ... + + public File workingDirectory; + public File storageDirectory; // for permanent storage (binary archive) + public boolean userSpecifiedACacheDir; + public String static_exeDirName; + private String login; + private String password; + private int maxUploadingJob; + private int nbCores; + private ComputeType computeMethod; + private GPUDevice GPUDevice; + private boolean printLog; + public List> requestTime; + private String extras; + + public Configuration(File cache_dir_, String login_, String password_) { + this.login = login_; + this.password = password_; + this.static_exeDirName = "exe"; + this.maxUploadingJob = 1; + this.nbCores = -1; // ie not set + this.computeMethod = ComputeType.CPU_GPU; + this.GPUDevice = null; + this.userSpecifiedACacheDir = false; + this.workingDirectory = null; + this.storageDirectory = null; + this.setCacheDir(cache_dir_); + this.printLog = false; + this.requestTime = null; + this.extras = ""; + } + + public String toString() { + return String.format("Configuration (workingDirectory '%s')", this.workingDirectory.getAbsolutePath()); + } + + public String login() { + return this.login; + } + + public String password() { + return this.password; + } + + public int maxUploadingJob() { + return this.maxUploadingJob; + } + + public GPUDevice getGPUDevice() { + return this.GPUDevice; + } + + public void setMaxUploadingJob(int max) { + this.maxUploadingJob = max; + } + + public void setUseNbCores(int nbcores) { + this.nbCores = nbcores; + } + + public int getNbCores() { + return this.nbCores; + } + + public void setPrintLog(boolean val) { + this.printLog = val; + } + + public boolean getPrintLog() { + return this.printLog; + } + + public int computeMethodToInt() { + return this.computeMethod.ordinal(); + } + + public ComputeType getComputeMethod() { + return this.computeMethod; + } + + public void setUseGPU(GPUDevice device) { + this.GPUDevice = device; + if (device == null) { + this.computeMethod = ComputeType.CPU_GPU; + } + } + + public void setComputeMethod(ComputeType meth) { + this.computeMethod = meth; + } + + public void setCacheDir(File cache_dir_) { + removeWorkingDirectory(); + if (cache_dir_ == null) { + this.userSpecifiedACacheDir = false; + try { + this.workingDirectory = File.createTempFile("farm_", ""); + this.workingDirectory.createNewFile(); // hoho... + this.workingDirectory.delete(); // hoho + this.workingDirectory.mkdir(); + this.workingDirectory.deleteOnExit(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + else { + this.userSpecifiedACacheDir = true; + this.workingDirectory = cache_dir_; + } + + } + + public void setStorageDir(File dir) { + if (dir != null) { + if (dir.exists() == false) { + dir.mkdir(); + } + this.storageDirectory = dir; + } + } + + public File getStorageDir() { + if (this.storageDirectory == null) { + return this.workingDirectory; + } + else { + return this.storageDirectory; + } + } + + public void setExtras(String str) { + this.extras = str; + } + + public String getExtras() { + return this.extras; + } + + public void cleanWorkingDirectory() { + this.cleanDirectory(this.workingDirectory); + this.cleanDirectory(this.storageDirectory); + } + + public boolean cleanDirectory(File dir) { + if (dir == null) { + return false; + } + + File[] files = dir.listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.isDirectory()) { + Utils.delete(file); + } + else { + try { + String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase(); + String name = file.getName().substring(0, file.getName().length() - 1 * extension.length()); + if (extension.equals(".zip")) { + // check if the md5 of the file is ok + String md5_local = Utils.md5(file.getAbsolutePath()); + + if (md5_local.equals(name) == false) { + System.err.println("cleanDirectory find an partial file => remove (" + file.getAbsolutePath() + ")"); + file.delete(); + } + + // TODO: remove old one + } + else { + file.delete(); + } + } + catch (StringIndexOutOfBoundsException e) { // because the file does not have an . in his path + file.delete(); + } + } + } + } + return true; + } + + public void removeWorkingDirectory() { + if (this.userSpecifiedACacheDir == true) { + this.cleanWorkingDirectory(); + } + else { + Utils.delete(this.workingDirectory); + } + } + + public List getLocalCacheFiles() { + List files_local = new LinkedList(); + List files = new LinkedList(); + if (this.workingDirectory != null) { + files.addAll(Arrays.asList(this.workingDirectory.listFiles())); + } + if (this.storageDirectory != null) { + files.addAll(Arrays.asList(this.storageDirectory.listFiles())); + } + + for (File file : files) { + if (file.isFile()) { + try { + String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase(); + String name = file.getName().substring(0, file.getName().length() - 1 * extension.length()); + if (extension.equals(".zip")) { + // check if the md5 of the file is ok + String md5_local = Utils.md5(file.getAbsolutePath()); + + if (md5_local.equals(name)) { + files_local.add(file); + } + } + } + catch (StringIndexOutOfBoundsException e) { // because the file does not have an . his path + } + } + } + return files_local; + } + + public String getJarVersion() { + String versionPath = "/VERSION"; + + InputStream versionStream = Client.class.getResourceAsStream(versionPath); + if (versionStream == null) { + System.err.println("Configuration::getJarVersion Failed to get version file"); + return ""; + } + + try { + InputStreamReader reader = new InputStreamReader(versionStream); + BufferedReader in = new BufferedReader(reader); + String version = in.readLine(); + + return version; + } + catch (IOException ex) { + System.err.println("Configuration::getJarVersion error while reading manifest file (" + versionPath + "): " + ex.getMessage()); + return ""; + } + } +} diff --git a/src/com/sheepit/client/Error.java b/src/com/sheepit/client/Error.java new file mode 100644 index 0000000..a2cae96 --- /dev/null +++ b/src/com/sheepit/client/Error.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2013-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client; + +public class Error { + public enum Type { + OK, + WRONG_CONFIGURATION, + AUTHENTICATION_FAILED, + TOO_OLD_CLIENT, + SESSION_DISABLED, + MISSING_RENDER, + MISSING_SCENE, + NOOUTPUTFILE, + DOWNLOAD_FILE, + CAN_NOT_CREATE_DIRECTORY, + NETWORK_ISSUE, RENDERER_CRASHED, + RENDERER_MISSING_LIBRARIES, + FAILED_TO_EXECUTE, + UNKNOWN + }; + + public enum ServerCode { + OK(0), + UNKNOWN(999), + + CONFIGURATION_ERROR_NO_CLIENT_VERSION_GIVEN(100), + CONFIGURATION_ERROR_CLIENT_TOO_OLD(101), + CONFIGURATION_ERROR_AUTH_FAILED(102), + CONFIGURATION_ERROR_WEB_SESSION_EXPIRATED(103), + CONFIGURATION_ERROR_MISSING_PARAMETER(104), + + JOB_REQUEST_NOJOB(200), + JOB_REQUEST_ERROR_NO_RENDERING_RIGHT(201), + JOB_REQUEST_ERROR_DEAD_SESSION(202), + JOB_REQUEST_ERROR_SESSION_DISABLED(203), + JOB_REQUEST_ERROR_INTERNAL_ERROR(204), + + JOB_VALIDATION_ERROR_MISSING_PARAMETER(300), + JOB_VALIDATION_ERROR_BROKEN_MACHINE(301), // in GPU the generated frame is black + JOB_VALIDATION_ERROR_FRAME_IS_NOT_IMAGE(302), + JOB_VALIDATION_ERROR_UPLOAD_FAILED(303), + JOB_VALIDATION_ERROR_SESSION_DISABLED(304), // missing heartbeat or broken machine + + KEEPMEALIVE_STOP_RENDERING(400), + + // internal error handling + ERROR_NO_ROOT(2), + ERROR_REQUEST_FAILED(5); + + private final int id; + + private ServerCode(int id) { + this.id = id; + } + + public int getValue() { + return id; + } + + public static ServerCode fromInt(int val) { + ServerCode[] As = ServerCode.values(); + for (int i = 0; i < As.length; i++) { + if (As[i].getValue() == val) { + return As[i]; + } + } + return ServerCode.UNKNOWN; + } + } + + public static Type ServerCodeToType(ServerCode sc) { + switch (sc) { + case OK: + return Type.OK; + case UNKNOWN: + return Type.UNKNOWN; + case CONFIGURATION_ERROR_CLIENT_TOO_OLD: + return Type.TOO_OLD_CLIENT; + case CONFIGURATION_ERROR_AUTH_FAILED: + return Type.AUTHENTICATION_FAILED; + + case CONFIGURATION_ERROR_NO_CLIENT_VERSION_GIVEN: + case CONFIGURATION_ERROR_WEB_SESSION_EXPIRATED: + return Type.WRONG_CONFIGURATION; + + case JOB_REQUEST_ERROR_SESSION_DISABLED: + case JOB_VALIDATION_ERROR_SESSION_DISABLED: + return Type.SESSION_DISABLED; + + default: + return Type.UNKNOWN; + } + } + + public static String humainString(Type in) { + switch (in) { + case TOO_OLD_CLIENT: + return "This client is too old, you need to update it"; + case AUTHENTICATION_FAILED: + return "Failed to authenticate, please check your login and password"; + case NOOUTPUTFILE: + return "Renderer have generated no output file, it's mostly a wrong project configuration or your are missing required libraries. Will try an another project in few minutes."; + case RENDERER_CRASHED: + return "Renderer have crashed. It's mostly due to a bad project or not enough memory. There is nothing you can do about it. Will try an another project in few minutes."; + case RENDERER_MISSING_LIBRARIES: + return "Failed to launch runderer. Please check if you have necessary libraries installed and if you have enough free place in working directory."; + case SESSION_DISABLED: + return "The server have disabled your session. It's mostly because your client generate broken frame (gpu not compatible for example)."; + default: + return in.toString(); + } + } +} diff --git a/src/com/sheepit/client/Gui.java b/src/com/sheepit/client/Gui.java new file mode 100644 index 0000000..b6916de --- /dev/null +++ b/src/com/sheepit/client/Gui.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010-2013 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client; + +public interface Gui { + public abstract void start(); + + public abstract void stop(); + + public abstract void status(String msg_); + + public void error(String err_); + + public void AddFrameRendered(); + + public void framesRemaining(int nb_); +} diff --git a/src/com/sheepit/client/Job.java b/src/com/sheepit/client/Job.java new file mode 100644 index 0000000..b954d20 --- /dev/null +++ b/src/com/sheepit/client/Job.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client; + +import java.io.File; + +public class Job { + private String numFrame; + private String sceneMD5; + private String rendererMD5; + private String id; + private String revision; + private String pictureFilename; + private String path; // path inside of the archive + private int renderDuration; // in second + private long memoryUsed; // in kB + private String rendererCommand; + private String script; + private int maxOutputNbLines; + private boolean useGPU; + private String extras; + private String updateRenderingStatusMethod; + + private Process process; + + private Configuration config; + + public Job(Configuration config_, String id_, String frame_, String revision_, String path_, boolean use_gpu, String command_, String script_, String sceneMd5_, String rendererMd5_, String extras_) { + config = config_; + id = id_; + numFrame = frame_; + revision = revision_; + path = path_; + useGPU = use_gpu; + rendererCommand = command_; + sceneMD5 = sceneMd5_; + rendererMD5 = rendererMd5_; + extras = extras_; + + pictureFilename = null; + renderDuration = 0; + memoryUsed = 0; + script = script_; + maxOutputNbLines = 0; + updateRenderingStatusMethod = null; + process = null; + + } + + public String toString() { + return String.format("Job (numFrame '%s' sceneMD5 '%s' rendererMD5 '%s' ID '%s' revision '%s' pictureFilename '%s' jobPath '%s' renderDuration '%s', memoryUsed %skB gpu %s extras '%s' updateRenderingStatusMethod '%s')", this.numFrame, this.sceneMD5, this.rendererMD5, this.id, this.revision, this.pictureFilename, this.path, this.renderDuration, this.memoryUsed, this.useGPU, this.extras, this.updateRenderingStatusMethod); + } + + public boolean workeable() { + return true; + } + + public String getId() { + return id; + } + + public String getFrameNumber() { + return numFrame; + } + + public String getExtras() { + return extras; + } + + public String getScript() { + return script; + } + + public String getSceneMD5() { + return sceneMD5; + } + + public String getRenderMd5() { + return rendererMD5; + } + + public String getPath() { + return path; + } + + public String getUpdateRenderingStatusMethod() { + return updateRenderingStatusMethod; + } + + public int getMaxOutputNbLines() { + return maxOutputNbLines; + } + + public void setProcess(Process val) { + process = val; + } + + public Process getProcess() { + return process; + } + + public long getMemoryUsed() { + return memoryUsed; + } + + public void setMemoryUsed(long val) { + memoryUsed = val; + } + + public int getRenderDuration() { + return renderDuration; + } + + public void setRenderDuration(int val) { + renderDuration = val; + } + + public void setMaxOutputNbLines(int val) { + maxOutputNbLines = val; + } + + public String getRenderCommand() { + return rendererCommand; + } + + public boolean getUseGPU() { + return useGPU; + } + + public String getRevision() { + return revision; + } + + public void setOutputImagePath(String path) { + pictureFilename = path; + } + + public String getOutputImagePath() { + return pictureFilename; + } + + public String getPrefixOutputImage() { + return id + "_"; + } + + public String getRendererDirectory() { + return config.workingDirectory.getAbsolutePath() + File.separator + rendererMD5; + } + + public String getRendererPath() { + return getRendererDirectory() + File.separator + "rend.exe"; + } + + public String getRendererArchivePath() { + return config.getStorageDir().getAbsolutePath() + File.separator + rendererMD5 + ".zip"; + } + + public String getSceneDirectory() { + return config.workingDirectory.getAbsolutePath() + File.separator + sceneMD5; + } + + public String getScenePath() { + return getSceneDirectory() + File.separator + this.path; + } + + public String getSceneArchivePath() { + return config.workingDirectory.getAbsolutePath() + File.separator + sceneMD5 + ".zip"; + } + + public boolean simultaneousUploadIsAllowed() { + // id 0 is power project + // id 1 is compute method project + // they are made to check is the computer can do render + return id.equals("0") == false && id.equals("1") == false; + } +} diff --git a/src/com/sheepit/client/Log.java b/src/com/sheepit/client/Log.java new file mode 100644 index 0000000..c10ada5 --- /dev/null +++ b/src/com/sheepit/client/Log.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2011-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class Log { + private static Log instance = null; + + private Map> checkpoints = new HashMap>();; + private int lastCheckPoint; + private DateFormat dateFormat; + + private boolean printStdOut; + + private Log(boolean print_) { + this.printStdOut = print_; + this.lastCheckPoint = 0; + this.checkpoints.put(this.lastCheckPoint, new ArrayList()); + this.dateFormat = new SimpleDateFormat("dd-MM kk:mm:ss"); + } + + public void debug(String msg_) { + this.append("debug", msg_); + } + + public void info(String msg_) { + this.append("info", msg_); + } + + public void error(String msg_) { + this.append("error", msg_); + } + + private void append(String level_, String msg_) { + if (msg_.equals("") == false) { + String line = this.dateFormat.format(new java.util.Date()) + " (" + level_ + ") " + msg_; + if (this.checkpoints.containsKey(this.lastCheckPoint)) { + this.checkpoints.get(this.lastCheckPoint).add(line); + } + if (this.printStdOut == true) { + System.out.println(line); + } + } + } + + public int newCheckPoint() { + int time = (int) (new Date().getTime()); + this.checkpoints.put(time, new ArrayList()); + this.lastCheckPoint = time; + return this.lastCheckPoint; + } + + public ArrayList getForCheckPoint(int point_) { + return this.checkpoints.get(point_); + } + + public void removeCheckPoint(int point_) { + try { + this.checkpoints.remove(point_); + } + catch (UnsupportedOperationException e) { + } + } + + public final synchronized static Log getInstance(Configuration config) { + if (instance == null) { + boolean print = false; + if (config != null) { + print = config.getPrintLog(); + } + instance = new Log(print); + } + return instance; + } + + public final synchronized static void printCheckPoint(int point_) { + Log log = Log.getInstance(null); + ArrayList logs = log.getForCheckPoint(point_); + Iterator it = logs.iterator(); + while (it.hasNext()) { + System.out.println(it.next()); + } + } +} diff --git a/src/com/sheepit/client/Pair.java b/src/com/sheepit/client/Pair.java new file mode 100644 index 0000000..d127cad --- /dev/null +++ b/src/com/sheepit/client/Pair.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.sheepit.client; + +import java.util.Objects; + +/** +* Container to ease passing around a tuple of two objects. This object provides a sensible +* implementation of equals(), returning true if equals() is true on each of the contained +* objects. +*/ +public class Pair { + public final F first; + public final S second; + + /** + * Constructor for a Pair. + * + * @param first the first object in the Pair + * @param second the second object in the pair + */ + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + + /** + * Checks the two objects for equality by delegating to their respective + * {@link Object#equals(Object)} methods. + * + * @param o the {@link Pair} to which this one is to be checked for equality + * @return true if the underlying objects of the Pair are both considered + * equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Pair)) { + return false; + } + Pair p = (Pair) o; + return Objects.equals(p.first, first) && Objects.equals(p.second, second); + } + + /** + * Compute a hash code using the hash codes of the underlying objects + * + * @return a hashcode of the Pair + */ + @Override + public int hashCode() { + return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); + } + + /** + * Convenience method for creating an appropriately typed pair. + * @param a the first object in the Pair + * @param b the second object in the pair + * @return a Pair that is templatized with the types of a and b + */ + public static Pair create(A a, B b) { + return new Pair(a, b); + } +} diff --git a/src/com/sheepit/client/Server.java b/src/com/sheepit/client/Server.java new file mode 100644 index 0000000..0f7c8bc --- /dev/null +++ b/src/com/sheepit/client/Server.java @@ -0,0 +1,794 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client; + +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.NoRouteToHostException; +import java.net.URL; +import java.net.URLEncoder; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import com.sheepit.client.Configuration.ComputeType; +import com.sheepit.client.Error.ServerCode; +import com.sheepit.client.exception.FermeException; +import com.sheepit.client.exception.FermeExceptionNoRightToRender; +import com.sheepit.client.exception.FermeExceptionNoSession; +import com.sheepit.client.exception.FermeExceptionSessionDisabled; +import com.sheepit.client.os.OS; + +public class Server extends Thread implements HostnameVerifier, X509TrustManager { + private String base_url; + private Configuration user_config; + private Client client; + private ArrayList cookies; + private HashMap pages; + private Log log; + private long lastRequestTime; + private int keepmealive_duration; // time is ms + + public Server(String url_, Configuration user_config_, Client client_) { + super(); + this.base_url = url_; + this.user_config = user_config_; + this.client = client_; + this.pages = new HashMap(); + this.cookies = new ArrayList(); + this.log = Log.getInstance(this.user_config); + this.lastRequestTime = 0; + this.keepmealive_duration = 15 * 60 * 1000; // default 15min + } + + public void run() { + this.stayAlive(); + } + + public void stayAlive() { + while (true) { + long current_time = new Date().getTime(); + if ((current_time - this.lastRequestTime) > this.keepmealive_duration) { + try { + String args = ""; + if (this.client != null && this.client.getRenderingJob() != null) { + args = "?frame=" + this.client.getRenderingJob().getFrameNumber() + "&job=" + this.client.getRenderingJob().getId(); + if (this.client.getRenderingJob().getExtras() != null && this.client.getRenderingJob().getExtras().length() > 0) { + args += "&extras=" + this.client.getRenderingJob().getExtras(); + } + } + + HttpURLConnection connection = this.HTTPRequest(this.base_url + "/server/keepmealive.php" + args); + + if (connection.getResponseCode() == HttpURLConnection.HTTP_OK && connection.getContentType().startsWith("text/xml")) { + DataInputStream in = new DataInputStream(connection.getInputStream()); + try { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in); + ServerCode ret = Utils.statusIsOK(document, "keepmealive"); + if (ret == ServerCode.KEEPMEALIVE_STOP_RENDERING) { + this.log.debug("Server::keeepmealive server ask to kill local render process"); + // kill the current process, it will generate an error but it's okay + if (this.client != null && this.client.getRenderingJob() != null && this.client.getRenderingJob().getProcess() != null) { + OS.getOS().kill(this.client.getRenderingJob().getProcess()); + } + } + } + catch (SAXException e) { + } + catch (ParserConfigurationException e) { + } + } + } + catch (IOException e) { + e.printStackTrace(); + } + } + try { + Thread.sleep(60 * 1000); // 1min + } + catch (InterruptedException e) { + return; + } + catch (Exception e) { + return; + } + } + } + + public String toString() { + return String.format("Server (base_url '%s', user_config %s, pages %s", this.base_url, this.user_config, this.pages); + } + + public Error.Type getConfiguration() { + OS os = OS.getOS(); + HttpURLConnection connection = null; + try { + String url_contents = String.format("%s%s?login=%s&password=%s&cpu_family=%s&cpu_model=%s&cpu_model_name=%s&cpu_cores=%s&os=%s&ram=%s&bits=%s&version=%s&extras=%s", + this.base_url, + "/server/config.php", + URLEncoder.encode(this.user_config.login(), "UTF-8"), + URLEncoder.encode(this.user_config.password(), "UTF-8"), + os.getCPU().family(), + os.getCPU().model(), + URLEncoder.encode(os.getCPU().name(), "UTF-8"), + ((this.user_config.getNbCores() == -1) ? os.getCPU().cores() : this.user_config.getNbCores()), + os.name(), + os.getMemory(), + os.getCPU().arch(), + this.user_config.getJarVersion(), + this.user_config.getExtras()); + this.log.debug("Server::getConfiguration url " + url_contents); + + connection = this.HTTPRequest(url_contents); + int r = connection.getResponseCode(); + String contentType = connection.getContentType(); + + if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/xml")) { + DataInputStream in = new DataInputStream(connection.getInputStream()); + Document document = null; + + try { + document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in); + } + catch (SAXException e) { + this.log.error("getConfiguration error: failed to parse XML SAXException " + e); + return Error.Type.WRONG_CONFIGURATION; + } + catch (IOException e) { + this.log.error("getConfiguration error: failed to parse XML IOException " + e); + return Error.Type.WRONG_CONFIGURATION; + } + catch (ParserConfigurationException e) { + this.log.error("getConfiguration error: failed to parse XML ParserConfigurationException " + e); + return Error.Type.WRONG_CONFIGURATION; + } + + ServerCode ret = Utils.statusIsOK(document, "config"); + if (ret != ServerCode.OK) { + return Error.ServerCodeToType(ret); + } + + Element config_node = null; + NodeList ns = null; + ns = document.getElementsByTagName("config"); + if (ns.getLength() == 0) { + this.log.error("getConfiguration error: failed to parse XML, no node 'config_serveur'"); + return Error.Type.WRONG_CONFIGURATION; + } + config_node = (Element) ns.item(0); + + ns = config_node.getElementsByTagName("request"); + if (ns.getLength() == 0) { + this.log.error("getConfiguration error: failed to parse XML, node 'config' have no child node 'request'"); + return Error.Type.WRONG_CONFIGURATION; + } + for (int i = 0; i < ns.getLength(); i++) { + Element element = (Element) ns.item(i); + if (element.hasAttribute("type") && element.hasAttribute("path")) { + this.pages.put(element.getAttribute("type"), element.getAttribute("path")); + if (element.getAttribute("type").equals("keepmealive") && element.hasAttribute("max-period")) { + this.keepmealive_duration = (Integer.parseInt(element.getAttribute("max-period")) - 120) * 1000; // put 2min of safety net + } + } + } + } + else { + this.log.error("Server::getConfiguration: Invalid response " + contentType + " " + r); + return Error.Type.WRONG_CONFIGURATION; + } + } + catch (ConnectException e) { + this.log.error("Server::getConfiguration error ConnectException " + e); + return Error.Type.NETWORK_ISSUE; + } + catch (UnknownHostException e) { + this.log.error("Server::getConfiguration error UnknownHostException " + e); + return Error.Type.NETWORK_ISSUE; + } + catch (NoRouteToHostException e) { + this.log.error("Server::getConfiguration error NoRouteToHost " + e); + return Error.Type.NETWORK_ISSUE; + } + catch (Exception e) { + this.log.error("Server::getConfiguration: exception 02R " + e); + e.printStackTrace(); + return Error.Type.UNKNOWN; + } + finally { + if (connection != null) { + connection.disconnect(); + } + } + return Error.Type.OK; + } + + public Job requestJob() throws FermeException, FermeExceptionNoRightToRender, FermeExceptionNoSession, FermeExceptionSessionDisabled { + this.log.debug("Server::requestJob"); + String url_contents = ""; + + HttpURLConnection connection = null; + try { + String url = String.format("%s?computemethod=%s", this.getPage("request-job"), this.user_config.computeMethodToInt()); + if (this.user_config.getComputeMethod() != ComputeType.CPU_ONLY && this.user_config.getGPUDevice() != null) { + String gpu_model = ""; + try { + gpu_model = URLEncoder.encode(this.user_config.getGPUDevice().getModel(), "UTF-8"); + } + catch (UnsupportedEncodingException e) { + } + url += "&gpu_model=" + gpu_model + "&gpu_ram=" + this.user_config.getGPUDevice().getMemory(); + } + + connection = this.HTTPRequest(url, this.generateXMLForMD5cache()); + + int r = connection.getResponseCode(); + String contentType = connection.getContentType(); + + if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/xml")) { + DataInputStream in = new DataInputStream(connection.getInputStream()); + Document document = null; + try { + document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in); + } + catch (SAXException e) { + throw new FermeException("error requestJob: parseXML failed, SAXException " + e); + } + catch (IOException e) { + throw new FermeException("error requestJob: parseXML failed IOException " + e); + } + catch (ParserConfigurationException e) { + throw new FermeException("error requestJob: parseXML failed ParserConfigurationException " + e); + } + + ServerCode ret = Utils.statusIsOK(document, "jobrequest"); + if (ret != ServerCode.OK) { + if (ret == ServerCode.JOB_REQUEST_NOJOB) { + handleFileMD5DeleteDocument(document, "jobrequest"); + return null; + } + else if (ret == ServerCode.JOB_REQUEST_ERROR_NO_RENDERING_RIGHT) { + throw new FermeExceptionNoRightToRender(); + } + else if (ret == ServerCode.JOB_REQUEST_ERROR_DEAD_SESSION) { + throw new FermeExceptionNoSession(); + } + else if (ret == ServerCode.JOB_REQUEST_ERROR_SESSION_DISABLED) { + throw new FermeExceptionSessionDisabled(); + } + this.log.error("Server::requestJob: Utils.statusIsOK(document, 'jobrequest') -> ret " + ret); + throw new FermeException("error requestJob: status is not ok (it's " + ret + ")"); + } + + handleFileMD5DeleteDocument(document, "jobrequest"); + + Element a_node = null; + NodeList ns = null; + + ns = document.getElementsByTagName("frames"); + if (ns.getLength() == 0) { + throw new FermeException("error requestJob: parseXML failed, no 'frame' node"); + } + a_node = (Element) ns.item(0); + + int remaining_frames = -1; + if (a_node.hasAttribute("remaining")) { + remaining_frames = Integer.parseInt(a_node.getAttribute("remaining")); + } + + ns = document.getElementsByTagName("job"); + if (ns.getLength() == 0) { + throw new FermeException("error requestJob: parseXML failed, no 'job' node"); + } + Element job_node = (Element) ns.item(0); + + ns = job_node.getElementsByTagName("renderer"); + if (ns.getLength() == 0) { + throw new FermeException("error requestJob: parseXML failed, node 'job' have no sub-node 'renderer'"); + } + Element renderer_node = (Element) ns.item(0); + + String script = "import bpy\nbpy.context.user_preferences.filepaths.temporary_directory = \"" + this.user_config.workingDirectory.getAbsolutePath().replace("\\", "\\\\") + "\"\n"; + try { + ns = job_node.getElementsByTagName("script"); + if (ns.getLength() != 0) { + Element a_node3 = (Element) ns.item(0); + script += new String(a_node3.getTextContent()); + } + } + catch (Exception e) { + e.printStackTrace(); + } + + String[] job_node_require_attribute = { "id", "archive_md5", "path", "revision", "use_gpu", "frame", "extras" }; + String[] renderer_node_require_attribute = { "md5", "commandline" }; + + for (String e : job_node_require_attribute) { + if (job_node.hasAttribute(e) == false) { + throw new FermeException("error requestJob: parseXML failed, job_node have to attribute '" + e + "'"); + } + } + + for (String e : renderer_node_require_attribute) { + if (renderer_node.hasAttribute(e) == false) { + throw new FermeException("error requestJob: parseXML failed, renderer_node have to attribute '" + e + "'"); + } + } + + boolean use_gpu = (job_node.getAttribute("use_gpu").compareTo("1") == 0); + + String frame_extras = ""; + if (job_node.hasAttribute("extras")) { + frame_extras = job_node.getAttribute("extras"); + } + + Job a_job = new Job( + this.user_config, + job_node.getAttribute("id"), + job_node.getAttribute("frame"), + job_node.getAttribute("revision"), + job_node.getAttribute("path").replace("/", File.separator), + use_gpu, + renderer_node.getAttribute("commandline"), + script, + job_node.getAttribute("archive_md5"), + renderer_node.getAttribute("md5"), + frame_extras + ); + + this.client.getGui().framesRemaining(remaining_frames); + + return a_job; + } + else { + System.out.println("Server::requestJob url " + url_contents + " r " + r + " contentType " + contentType); + InputStream in = connection.getInputStream(); + String line; + BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); + while ((line = reader.readLine()) != null) { + System.out.print(line); + } + System.out.println(""); + } + } + catch (FermeExceptionNoRightToRender e) { + throw e; + } + catch (FermeExceptionNoSession e) { + throw e; + } + catch (FermeExceptionSessionDisabled e) { + throw e; + } + catch (FermeException e) { + throw new FermeException(e.getMessage()); + } + catch (Exception e) { + throw new FermeException("error requestJob: unknow exception " + e); + } + finally { + if (connection != null) { + connection.disconnect(); + } + } + throw new FermeException("error requestJob, end of function"); + } + + public HttpURLConnection HTTPRequest(String url_) throws IOException { + return this.HTTPRequest(url_, null); + } + + public HttpURLConnection HTTPRequest(String url_, String data_) throws IOException { + this.log.debug("Server::HTTPRequest url(" + url_ + ")"); + HttpURLConnection connection = null; + URL url = new URL(url_); + + connection = (HttpURLConnection) url.openConnection(); + connection.setDoInput(true); + connection.setDoOutput(true); + connection.setRequestMethod("GET"); + for (String cookie : this.cookies) { + connection.setRequestProperty("Cookie", cookie); + } + + if (url_.startsWith("https://")) { + try { + SSLContext sc; + sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[] { this }, null); + SSLSocketFactory factory = sc.getSocketFactory(); + ((HttpsURLConnection) connection).setSSLSocketFactory(factory); + ((HttpsURLConnection) connection).setHostnameVerifier(this); + } + catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + catch (KeyManagementException e) { + e.printStackTrace(); + return null; + } + } + + if (data_ != null) { + connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded"); + connection.setRequestMethod("POST"); + OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream()); + out.write(data_); + out.flush(); + out.close(); + } + + String headerName = null; + for (int i = 1; (headerName = connection.getHeaderFieldKey(i)) != null; i++) { + if (headerName.equals("Set-Cookie")) { + String cookie = connection.getHeaderField(i); + + boolean cookieIsPresent = false; + for (String value : this.cookies) { + if (value.equalsIgnoreCase(cookie)) + cookieIsPresent = true; + } + if (!cookieIsPresent) + this.cookies.add(cookie); + } + } + + this.lastRequestTime = new Date().getTime(); + + return connection; + } + + public int HTTPGetFile(String url_, String destination_, Gui gui_, String status_) { + // the destination_ parent directory must exist + try { + HttpURLConnection httpCon = this.HTTPRequest(url_); + + InputStream inStrm = httpCon.getInputStream(); + if (httpCon.getResponseCode() != HttpURLConnection.HTTP_OK) { + return -1; + } + int size = httpCon.getContentLength(); + long start = new Date().getTime(); + + FileOutputStream fos = new FileOutputStream(destination_); + byte[] ch = new byte[512 * 1024]; + int nb; + long writed = 0; + long last_gui_update = 0; // size in byte + while ((nb = inStrm.read(ch)) != -1) { + fos.write(ch, 0, nb); + writed += nb; + if ((writed - last_gui_update) > 1000000) { // only update the gui every 1MB + gui_.status(String.format(status_, (int) (100.0 * writed / size))); + last_gui_update = writed; + } + } + fos.close(); + inStrm.close(); + long end = new Date().getTime(); + System.out.println(String.format("File downloaded at %.1f kB/s", ((float) (size / 1000)) / ((float) (end - start) / 1000))); + this.lastRequestTime = new Date().getTime(); + return 0; + } + catch (Exception e) { + System.err.println("Server::HTTPGetFile exception"); + e.printStackTrace(); + } + return -2; + } + + public ServerCode HTTPSendFile(String surl, String file1) { + this.log.debug("Server::HTTPSendFile(" + surl + "," + file1 + ")"); + + HttpURLConnection conn = null; + DataOutputStream dos = null; + BufferedReader inStream = null; + + String exsistingFileName = file1; + File fFile2Snd = new File(exsistingFileName); + + String lineEnd = "\r\n"; + String twoHyphens = "--"; + String boundary = "***232404jkg4220957934FW**"; + + int bytesRead, bytesAvailable, bufferSize; + byte[] buffer; + int maxBufferSize = 1 * 1024 * 1024; + + String urlString = surl; + + try { + FileInputStream fileInputStream = new FileInputStream(new File(exsistingFileName)); + URL url = new URL(urlString); + + conn = (HttpURLConnection) url.openConnection(); + conn.setDoInput(true); + conn.setDoOutput(true); + conn.setUseCaches(false); + for (String cookie : this.cookies) { + conn.setRequestProperty("Cookie", cookie); + } + + conn.setRequestMethod("POST"); + conn.setRequestProperty("Connection", "Keep-Alive"); + conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); + + if (urlString.startsWith("https://")) { + try { + SSLContext sc; + sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[] { this }, null); + SSLSocketFactory factory = sc.getSocketFactory(); + ((HttpsURLConnection) conn).setSSLSocketFactory(factory); + ((HttpsURLConnection) conn).setHostnameVerifier(this); + } + catch (NoSuchAlgorithmException e) { + this.log.error("Server::HTTPSendFile, exception NoSuchAlgorithmException " + e); + try { + fileInputStream.close(); + } + catch (Exception e1) { + + } + return ServerCode.UNKNOWN; + } + catch (KeyManagementException e) { + this.log.error("Server::HTTPSendFile, exception KeyManagementException " + e); + try { + fileInputStream.close(); + } + catch (Exception e1) { + + } + return ServerCode.UNKNOWN; + } + } + + dos = new DataOutputStream(conn.getOutputStream()); + dos.writeBytes(twoHyphens + boundary + lineEnd); + dos.writeBytes("Content-Disposition: form-data; name=\"file\";" + " filename=\"" + fFile2Snd.getName() + "\"" + lineEnd); + dos.writeBytes(lineEnd); + + bytesAvailable = fileInputStream.available(); + bufferSize = Math.min(bytesAvailable, maxBufferSize); + buffer = new byte[bufferSize]; + + bytesRead = fileInputStream.read(buffer, 0, bufferSize); + + while (bytesRead > 0) { + dos.write(buffer, 0, bufferSize); + bytesAvailable = fileInputStream.available(); + bufferSize = Math.min(bytesAvailable, maxBufferSize); + bytesRead = fileInputStream.read(buffer, 0, bufferSize); + } + + dos.writeBytes(lineEnd); + dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); + fileInputStream.close(); + dos.flush(); + dos.close(); + } + catch (MalformedURLException ex) { + this.log.error("Server::HTTPSendFile, exception MalformedURLException " + ex); + return ServerCode.UNKNOWN; + } + catch (IOException ioe) { + this.log.error("Server::HTTPSendFile, exception IOException " + ioe); + return ServerCode.UNKNOWN; + } + catch (Exception e6) { + this.log.error("Server::HTTPSendFile, exception Exception " + e6); + return ServerCode.UNKNOWN; + } + + int r; + try { + r = conn.getResponseCode(); + } + catch (IOException e1) { + e1.printStackTrace(); + return ServerCode.UNKNOWN; + } + String contentType = conn.getContentType(); + + if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/xml")) { + DataInputStream in; + try { + in = new DataInputStream(conn.getInputStream()); + } + catch (IOException e1) { + e1.printStackTrace(); + return ServerCode.UNKNOWN; + } + Document document = null; + try { + document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in); + } + catch (SAXException e) { + e.printStackTrace(); + return ServerCode.UNKNOWN; + } + catch (IOException e) { + e.printStackTrace(); + return ServerCode.UNKNOWN; + } + catch (ParserConfigurationException e) { + e.printStackTrace(); + return ServerCode.UNKNOWN; + } + + this.lastRequestTime = new Date().getTime(); + + ServerCode ret1 = Utils.statusIsOK(document, "jobvalidate"); + if (ret1 != ServerCode.OK) { + this.log.error("Server::HTTPSendFile wrong status (is " + ret1 + ")"); + return ret1; + } + return ServerCode.OK; + } + else { + try { + inStream = new BufferedReader(new InputStreamReader(conn.getInputStream())); + + String str; + while ((str = inStream.readLine()) != null) { + System.out.println(str); + System.out.println(""); + } + inStream.close(); + } + catch (IOException ioex) { + } + } + return ServerCode.UNKNOWN; + } + + private String generateXMLForMD5cache() { + String xml_str = null; + try { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document document_cache = docBuilder.newDocument(); + + Element rootElement = document_cache.createElement("cache"); + document_cache.appendChild(rootElement); + + List local_files = this.user_config.getLocalCacheFiles(); + for (File local_file : local_files) { + Element node_file = document_cache.createElement("file"); + rootElement.appendChild(node_file); + try { + String extension = local_file.getName().substring(local_file.getName().lastIndexOf('.')).toLowerCase(); + String name = local_file.getName().substring(0, local_file.getName().length() - 1 * extension.length()); + if (extension.equals(".zip")) { + node_file.setAttribute("md5", name); + } + } + catch (StringIndexOutOfBoundsException e) { // because the file does not have an . his path + } + } + + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(document_cache), new StreamResult(writer)); + xml_str = writer.getBuffer().toString(); + } + catch (TransformerConfigurationException e) { + this.log.debug("Server::generateXMLForMD5cache " + e); + } + catch (TransformerException e) { + this.log.debug("Server::generateXMLForMD5cache " + e); + } + catch (ParserConfigurationException e) { + this.log.debug("Server::generateXMLForMD5cache " + e); + } + + return xml_str; + } + + private void handleFileMD5DeleteDocument(Document document, String root_nodename) { + NodeList ns = document.getElementsByTagName(root_nodename); + if (ns.getLength() > 0) { + Element root_node = (Element) ns.item(0); + ns = root_node.getElementsByTagName("file"); + if (ns.getLength() > 0) { + for (int i = 0; i < ns.getLength(); ++i) { + Element file_node = (Element) ns.item(i); + if (file_node.hasAttribute("md5") && file_node.hasAttribute("action") && file_node.getAttribute("action").equals("delete")) { + String path = this.user_config.workingDirectory.getAbsolutePath() + File.separatorChar + file_node.getAttribute("md5"); + this.log.debug("Server::handleFileMD5DeleteDocument delete old file " + path); + File file_to_delete = new File(path + ".zip"); + file_to_delete.delete(); + Utils.delete(new File(path)); + } + } + } + } + } + + public String getPage(String key) { + if (this.pages.containsKey(key)) { + return this.base_url + this.pages.get(key); + } + return ""; + } + + @Override + public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public boolean verify(String arg0, SSLSession arg1) { + return true; // trust every ssl certificate + } +} diff --git a/src/com/sheepit/client/ShutdownHook.java b/src/com/sheepit/client/ShutdownHook.java new file mode 100644 index 0000000..7b8ac75 --- /dev/null +++ b/src/com/sheepit/client/ShutdownHook.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client; + +public class ShutdownHook { + private Client _client; + + public ShutdownHook(Client client_) { + this._client = client_; + } + + public void attachShutDownHook() { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + _client.stop(); + } + }); + } +} diff --git a/src/com/sheepit/client/Utils.java b/src/com/sheepit/client/Utils.java new file mode 100644 index 0000000..2473eeb --- /dev/null +++ b/src/com/sheepit/client/Utils.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import com.sheepit.client.Error.ServerCode; + +public class Utils { + public static int unzipFileIntoDirectory(String zipFileName_, String jiniHomeParentDirName_) { + File rootdir = new File(jiniHomeParentDirName_); + try { + ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFileName_)); + byte[] buffer = new byte[4096]; + ZipEntry ze; + while ((ze = zis.getNextEntry()) != null) { + FileOutputStream fos = null; + try { + File f = new File(rootdir.getAbsolutePath() + File.separator + ze.getName()); + if (ze.isDirectory()) { + f.mkdirs(); + continue; + } + else { + f.getParentFile().mkdirs(); + f.createNewFile(); + try { + f.setExecutable(true); + } + catch (NoSuchMethodError e2) { + // do nothing it's related to the filesystem + } + } + + fos = new FileOutputStream(f); + int numBytes; + while ((numBytes = zis.read(buffer, 0, buffer.length)) != -1) { + fos.write(buffer, 0, numBytes); + } + fos.close(); + } + catch (Exception e1) { + e1.printStackTrace(); + } + if (fos != null) { + try { + fos.close(); + } + catch (IOException e) { + } + } + zis.closeEntry(); + } + } + catch (IllegalArgumentException e) { + Log logger = Log.getInstance(null); // might not print the log since the config is null + logger.error("Utils::unzipFileIntoDirectory(" + zipFileName_ + "," + jiniHomeParentDirName_ + ") exception " + e); + return -2; + } + catch (Exception e) { + Log logger = Log.getInstance(null); // might not print the log since the config is null + logger.error("Utils::unzipFileIntoDirectory(" + zipFileName_ + "," + jiniHomeParentDirName_ + ") exception " + e); + return -1; + } + return 0; + } + + public static String md5(String path_of_file_) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException e1) { + e1.printStackTrace(); + return ""; + } + File f = new File(path_of_file_); + InputStream is; + try { + is = new FileInputStream(f); + } + catch (FileNotFoundException e1) { + e1.printStackTrace(); + return ""; + } + byte[] buffer = new byte[8192]; + int read = 0; + try { + while ((read = is.read(buffer)) > 0) { + digest.update(buffer, 0, read); + } + byte[] md5sum = digest.digest(); + BigInteger bigInt = new BigInteger(1, md5sum); + + String output = bigInt.toString(16); + + // fill with "0" because bigInt.toString does not add 0 at the beginning of the result + int zero_to_add = 32 - output.length(); + for (int i = 0; i < zero_to_add; i++) + output = "0" + output; + + return output; + } + catch (IOException e) { + e.printStackTrace(); + } + finally { + try { + is.close(); + } + catch (IOException e) { + e.printStackTrace(); + //throw new RuntimeException("Unable to close input stream for MD5 calculation", e); + } + } + return ""; + } + + public static double lastModificationTime(File directory_) { + double max = 0.0; + if (directory_.isDirectory()) { + File[] list = directory_.listFiles(); + if (list != null) { + for (int i = 0; i < list.length; i++) { + double max1 = lastModificationTime(list[i]); + if (max1 > max) { + max = max1; + } + } + } + } + else if (directory_.isFile()) { + return directory_.lastModified(); + } + + return max; + } + + public static ServerCode statusIsOK(Document document_, String rootname_) { + NodeList ns = document_.getElementsByTagName(rootname_); + if (ns.getLength() == 0) { + return Error.ServerCode.ERROR_NO_ROOT; + } + Element a_node = (Element) ns.item(0); + if (a_node.hasAttribute("status")) { + return Error.ServerCode.fromInt(Integer.parseInt(a_node.getAttribute("status"))); + } + return Error.ServerCode.UNKNOWN; + } + + /** + * Will recursively delete a directory + */ + public static void delete(File file) { + if (file == null) { + return; + } + if (file.isDirectory()) { + String files[] = file.list(); + if (files != null) { + if (files.length != 0) { + for (String temp : files) { + File fileDelete = new File(file, temp); + delete(fileDelete); + } + } + } + } + file.delete(); + } + + public static long parseNumber(String in) { + in = in.trim(); + in = in.replaceAll(",", "."); + try { + return Long.parseLong(in); + } + catch (NumberFormatException e) { + } + final Matcher m = Pattern.compile("([\\d.,]+)\\s*(\\w)").matcher(in); + m.find(); + int scale = 1; + switch (m.group(2).charAt(0)) { + case 'G': + scale *= 1000; + case 'g': + scale *= 1000; + case 'M': + scale *= 1000; + case 'm': + scale *= 1000; + case 'K': + break; + default: + return 0; + } + return Math.round(Double.parseDouble(m.group(1)) * scale); + } + + public static T[] concatAll(T[] first, T[]... rest) { + int totalLength = first.length; + for (T[] array : rest) { + totalLength += array.length; + } + T[] result = Arrays.copyOf(first, totalLength); + int offset = first.length; + for (T[] array : rest) { + System.arraycopy(array, 0, result, offset, array.length); + offset += array.length; + } + return result; + } +} diff --git a/src/com/sheepit/client/exception/FermeException.java b/src/com/sheepit/client/exception/FermeException.java new file mode 100644 index 0000000..6eda682 --- /dev/null +++ b/src/com/sheepit/client/exception/FermeException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010-2013 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.exception; + +public class FermeException extends Exception { + public FermeException() { + super(); + } + + public FermeException(String message_) { + super(message_); + } +} diff --git a/src/com/sheepit/client/exception/FermeExceptionNoRightToRender.java b/src/com/sheepit/client/exception/FermeExceptionNoRightToRender.java new file mode 100644 index 0000000..3d5ec6e --- /dev/null +++ b/src/com/sheepit/client/exception/FermeExceptionNoRightToRender.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.exception; + +public class FermeExceptionNoRightToRender extends FermeException { + public FermeExceptionNoRightToRender() { + super(); + } + + public FermeExceptionNoRightToRender(String message_) { + super(message_); + } +} diff --git a/src/com/sheepit/client/exception/FermeExceptionNoSession.java b/src/com/sheepit/client/exception/FermeExceptionNoSession.java new file mode 100644 index 0000000..4b6e17e --- /dev/null +++ b/src/com/sheepit/client/exception/FermeExceptionNoSession.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.exception; + +public class FermeExceptionNoSession extends FermeException { + public FermeExceptionNoSession() { + super(); + } + + public FermeExceptionNoSession(String message_) { + super(message_); + } +} diff --git a/src/com/sheepit/client/exception/FermeExceptionSessionDisabled.java b/src/com/sheepit/client/exception/FermeExceptionSessionDisabled.java new file mode 100644 index 0000000..3997ec1 --- /dev/null +++ b/src/com/sheepit/client/exception/FermeExceptionSessionDisabled.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.exception; + +public class FermeExceptionSessionDisabled extends FermeException { + public FermeExceptionSessionDisabled() { + super(); + } + + public FermeExceptionSessionDisabled(String message_) { + super(message_); + } +} diff --git a/src/com/sheepit/client/hardware/cpu/CPU.java b/src/com/sheepit/client/hardware/cpu/CPU.java new file mode 100644 index 0000000..e114b8d --- /dev/null +++ b/src/com/sheepit/client/hardware/cpu/CPU.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.hardware.cpu; + +public class CPU { + private String name; + private String model; + private String family; + private String arch; // 32 or 64 bits + + public CPU() { + this.name = null; + this.model = null; + this.family = null; + this.generateArch(); + } + + public String name() { + return this.name; + } + + public String model() { + return this.model; + } + + public String family() { + return this.family; + } + + public String arch() { + return this.arch; + } + + public int cores() { + return Runtime.getRuntime().availableProcessors(); + } + + public void setName(String name_) { + this.name = name_; + } + + public void setModel(String model_) { + this.model = model_; + } + + public void setFamily(String family_) { + this.family = family_; + } + + public void setArch(String arch_) { + this.arch = arch_; + } + + public void generateArch() { + String arch = System.getProperty("os.arch").toLowerCase(); + if (arch.equals("i386") || arch.equals("i686") || arch.equals("x86")) { + this.arch = "32bit"; + } + else if (arch.equals("amd64") || arch.equals("x86_64")) { + this.arch = "64bit"; + } + else { + this.arch = "xxbit"; + } + } +} diff --git a/src/com/sheepit/client/hardware/gpu/CUDA.java b/src/com/sheepit/client/hardware/gpu/CUDA.java new file mode 100644 index 0000000..3f824fa --- /dev/null +++ b/src/com/sheepit/client/hardware/gpu/CUDA.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.hardware.gpu; + +import com.sun.jna.Library; +import com.sun.jna.NativeLong; + +public interface CUDA extends Library { + public int cuInit(int flags); + + /* + * @return: CUDA_SUCCESS, CUDA_ERROR_DEINITIALIZED, CUDA_ERROR_NOT_INITIALIZED, CUDA_ERROR_INVALID_CONTEXT, CUDA_ERROR_INVALID_VALUE + */ + public int cuDeviceGetCount(int count[]); + + public int cuDeviceGetName(byte[] name, int len, int dev); + + public int cuDeviceTotalMem(NativeLong bytes[], int dev); +} diff --git a/src/com/sheepit/client/hardware/gpu/CUresult.java b/src/com/sheepit/client/hardware/gpu/CUresult.java new file mode 100644 index 0000000..c2724b2 --- /dev/null +++ b/src/com/sheepit/client/hardware/gpu/CUresult.java @@ -0,0 +1,508 @@ +/* + * JCuda - Java bindings for NVIDIA CUDA driver and runtime API + * + * Copyright (c) 2009-2012 Marco Hutter - http://www.jcuda.org + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +//package jcuda.driver; +package com.sheepit.client.hardware.gpu; + +/** + * Error codes.
+ *
+ * Most comments are taken from the CUDA reference manual. + */ +public class CUresult { + /** + * The API call returned with no errors. In the case of query calls, this + * can also mean that the operation being queried is complete (see + * ::cuEventQuery() and ::cuStreamQuery()). + */ + public static final int CUDA_SUCCESS = 0; + + /** + * This indicates that one or more of the parameters passed to the API call + * is not within an acceptable range of values. + */ + public static final int CUDA_ERROR_INVALID_VALUE = 1; + + /** + * The API call failed because it was unable to allocate enough memory to + * perform the requested operation. + */ + public static final int CUDA_ERROR_OUT_OF_MEMORY = 2; + + /** + * This indicates that the CUDA driver has not been initialized with + * ::cuInit() or that initialization has failed. + */ + public static final int CUDA_ERROR_NOT_INITIALIZED = 3; + + /** + * This indicates that the CUDA driver is in the process of shutting down. + */ + public static final int CUDA_ERROR_DEINITIALIZED = 4; + + /** + * This indicates profiling APIs are called while application is running + * in visual profiler mode. + */ + public static final int CUDA_ERROR_PROFILER_DISABLED = 5; + + /** + * This indicates profiling has not been initialized for this context. + * Call cuProfilerInitialize() to resolve this. + * @deprecated This error return is deprecated as of CUDA 5.0. + * It is no longer an error to attempt to enable/disable the + * profiling via ::cuProfilerStart or ::cuProfilerStop without + * initialization. + */ + public static final int CUDA_ERROR_PROFILER_NOT_INITIALIZED = 6; + + /** + * This indicates profiler has already been started and probably + * cuProfilerStart() is incorrectly called. + * @deprecated This error return is deprecated as of CUDA 5.0. + * It is no longer an error to call cuProfilerStart() when + * profiling is already enabled. + */ + public static final int CUDA_ERROR_PROFILER_ALREADY_STARTED = 7; + + /** + * This indicates profiler has already been stopped and probably + * cuProfilerStop() is incorrectly called. + * @deprecated This error return is deprecated as of CUDA 5.0. + * It is no longer an error to call cuProfilerStop() when + * profiling is already disabled. + */ + public static final int CUDA_ERROR_PROFILER_ALREADY_STOPPED = 8; + + /** + * This indicates that no CUDA-capable devices were detected by the installed + * CUDA driver. + */ + public static final int CUDA_ERROR_NO_DEVICE = 100; + + /** + * This indicates that the device ordinal supplied by the user does not + * correspond to a valid CUDA device. + */ + public static final int CUDA_ERROR_INVALID_DEVICE = 101; + + /** + * This indicates that the device kernel image is invalid. This can also + * indicate an invalid CUDA module. + */ + public static final int CUDA_ERROR_INVALID_IMAGE = 200; + + /** + * This most frequently indicates that there is no context bound to the + * current thread. This can also be returned if the context passed to an + * API call is not a valid handle (such as a context that has had + * ::cuCtxDestroy() invoked on it). This can also be returned if a user + * mixes different API versions (i.e. 3010 context with 3020 API calls). + * See ::cuCtxGetApiVersion() for more details. + */ + public static final int CUDA_ERROR_INVALID_CONTEXT = 201; + + /** + * This indicated that the context being supplied as a parameter to the + * API call was already the active context. + * \deprecated + * This error return is deprecated as of CUDA 3.2. It is no longer an + * error to attempt to push the active context via ::cuCtxPushCurrent(). + */ + public static final int CUDA_ERROR_CONTEXT_ALREADY_CURRENT = 202; + + /** + * This indicates that a map or register operation has failed. + */ + public static final int CUDA_ERROR_MAP_FAILED = 205; + + /** + * This indicates that an unmap or unregister operation has failed. + */ + public static final int CUDA_ERROR_UNMAP_FAILED = 206; + + /** + * This indicates that the specified array is currently mapped and thus + * cannot be destroyed. + */ + public static final int CUDA_ERROR_ARRAY_IS_MAPPED = 207; + + /** + * This indicates that the resource is already mapped. + */ + public static final int CUDA_ERROR_ALREADY_MAPPED = 208; + + /** + * This indicates that there is no kernel image available that is suitable + * for the device. This can occur when a user specifies code generation + * options for a particular CUDA source file that do not include the + * corresponding device configuration. + */ + public static final int CUDA_ERROR_NO_BINARY_FOR_GPU = 209; + + /** + * This indicates that a resource has already been acquired. + */ + public static final int CUDA_ERROR_ALREADY_ACQUIRED = 210; + + /** + * This indicates that a resource is not mapped. + */ + public static final int CUDA_ERROR_NOT_MAPPED = 211; + + /** + * This indicates that a mapped resource is not available for access as an + * array. + */ + public static final int CUDA_ERROR_NOT_MAPPED_AS_ARRAY = 212; + + /** + * This indicates that a mapped resource is not available for access as a + * pointer. + */ + public static final int CUDA_ERROR_NOT_MAPPED_AS_POINTER = 213; + + /** + * This indicates that an uncorrectable ECC error was detected during + * execution. + */ + public static final int CUDA_ERROR_ECC_UNCORRECTABLE = 214; + + /** + * This indicates that the ::CUlimit passed to the API call is not + * supported by the active device. + */ + public static final int CUDA_ERROR_UNSUPPORTED_LIMIT = 215; + + /** + * This indicates that the ::CUcontext passed to the API call can + * only be bound to a single CPU thread at a time but is already + * bound to a CPU thread. + */ + public static final int CUDA_ERROR_CONTEXT_ALREADY_IN_USE = 216; + + /** + * This indicates that peer access is not supported across the given + * devices. + */ + public static final int CUDA_ERROR_PEER_ACCESS_UNSUPPORTED = 217; + + /** + * This indicates that the device kernel source is invalid. + */ + public static final int CUDA_ERROR_INVALID_SOURCE = 300; + + /** + * This indicates that the file specified was not found. + */ + public static final int CUDA_ERROR_FILE_NOT_FOUND = 301; + + /** + * This indicates that a link to a shared object failed to resolve. + */ + public static final int CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND = 302; + + /** + * This indicates that initialization of a shared object failed. + */ + public static final int CUDA_ERROR_SHARED_OBJECT_INIT_FAILED = 303; + + /** + * This indicates that an OS call failed. + */ + public static final int CUDA_ERROR_OPERATING_SYSTEM = 304; + + /** + * This indicates that a resource handle passed to the API call was not + * valid. Resource handles are opaque types like ::CUstream and ::CUevent. + */ + public static final int CUDA_ERROR_INVALID_HANDLE = 400; + + /** + * This indicates that a named symbol was not found. Examples of symbols + * are global/constant variable names, texture names, and surface names. + */ + public static final int CUDA_ERROR_NOT_FOUND = 500; + + /** + * This indicates that asynchronous operations issued previously have not + * completed yet. This result is not actually an error, but must be indicated + * differently than ::CUDA_SUCCESS (which indicates completion). Calls that + * may return this value include ::cuEventQuery() and ::cuStreamQuery(). + */ + public static final int CUDA_ERROR_NOT_READY = 600; + + /** + * An exception occurred on the device while executing a kernel. Common + * causes include dereferencing an invalid device pointer and accessing + * out of bounds shared memory. The context cannot be used, so it must + * be destroyed (and a new one should be created). All existing device + * memory allocations from this context are invalid and must be + * reconstructed if the program is to continue using CUDA. + */ + public static final int CUDA_ERROR_LAUNCH_FAILED = 700; + + /** + * This indicates that a launch did not occur because it did not have + * appropriate resources. This error usually indicates that the user has + * attempted to pass too many arguments to the device kernel, or the + * kernel launch specifies too many threads for the kernel's register + * count. Passing arguments of the wrong size (i.e. a 64-bit pointer + * when a 32-bit int is expected) is equivalent to passing too many + * arguments and can also result in this error. + */ + public static final int CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES = 701; + + /** + * This indicates that the device kernel took too long to execute. This can + * only occur if timeouts are enabled - see the device attribute + * ::CU_DEVICE_ATTRIBUTE_KERNEL_EXEC_TIMEOUT for more information. The + * context cannot be used (and must be destroyed similar to + * ::CUDA_ERROR_LAUNCH_FAILED). All existing device memory allocations from + * this context are invalid and must be reconstructed if the program is to + * continue using CUDA. + */ + public static final int CUDA_ERROR_LAUNCH_TIMEOUT = 702; + + /** + * This error indicates a kernel launch that uses an incompatible texturing + * mode. + */ + public static final int CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING = 703; + + /** + * This error indicates that a call to ::cuCtxEnablePeerAccess() is + * trying to re-enable peer access to a context which has already + * had peer access to it enabled. + */ + public static final int CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED = 704; + + /** + * This error indicates that a call to ::cuMemPeerRegister is trying to + * register memory from a context which has not had peer access + * enabled yet via ::cuCtxEnablePeerAccess(), or that + * ::cuCtxDisablePeerAccess() is trying to disable peer access + * which has not been enabled yet. + */ + public static final int CUDA_ERROR_PEER_ACCESS_NOT_ENABLED = 705; + + /** + * This error indicates that a call to ::cuMemPeerRegister is trying to + * register already-registered memory. + * @deprecated This value has been added in CUDA 4.0 RC, + * and removed in CUDA 4.0 RC2 + */ + public static final int CUDA_ERROR_PEER_MEMORY_ALREADY_REGISTERED = 706; + + /** + * This error indicates that a call to ::cuMemPeerUnregister is trying to + * unregister memory that has not been registered. + * @deprecated This value has been added in CUDA 4.0 RC, + * and removed in CUDA 4.0 RC2 + */ + public static final int CUDA_ERROR_PEER_MEMORY_NOT_REGISTERED = 707; + + /** + * This error indicates that ::cuCtxCreate was called with the flag + * ::CU_CTX_PRIMARY on a device which already has initialized its + * primary context. + */ + public static final int CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE = 708; + + /** + * This error indicates that the context current to the calling thread + * has been destroyed using ::cuCtxDestroy, or is a primary context which + * has not yet been initialized. + */ + public static final int CUDA_ERROR_CONTEXT_IS_DESTROYED = 709; + + /** + * A device-side assert triggered during kernel execution. The context + * cannot be used anymore, and must be destroyed. All existing device + * memory allocations from this context are invalid and must be + * reconstructed if the program is to continue using CUDA. + */ + public static final int CUDA_ERROR_ASSERT = 710; + + /** + * This error indicates that the hardware resources required to enable + * peer access have been exhausted for one or more of the devices + * passed to ::cuCtxEnablePeerAccess(). + */ + public static final int CUDA_ERROR_TOO_MANY_PEERS = 711; + + /** + * This error indicates that the memory range passed to ::cuMemHostRegister() + * has already been registered. + */ + public static final int CUDA_ERROR_HOST_MEMORY_ALREADY_REGISTERED = 712; + + /** + * This error indicates that the pointer passed to ::cuMemHostUnregister() + * does not correspond to any currently registered memory region. + */ + public static final int CUDA_ERROR_HOST_MEMORY_NOT_REGISTERED = 713; + + /** + * This error indicates that the attempted operation is not permitted. + */ + public static final int CUDA_ERROR_NOT_PERMITTED = 800; + + /** + * This error indicates that the attempted operation is not supported + * on the current system or device. + */ + public static final int CUDA_ERROR_NOT_SUPPORTED = 801; + + /** + * This indicates that an unknown internal error has occurred. + */ + public static final int CUDA_ERROR_UNKNOWN = 999; + + /** + * Returns the String identifying the given CUresult + * + * @param result The CUresult value + * @return The String identifying the given CUresult + */ + public static String stringFor(int result) { + switch (result) { + case CUDA_SUCCESS: + return "CUDA_SUCCESS"; + case CUDA_ERROR_INVALID_VALUE: + return "CUDA_ERROR_INVALID_VALUE"; + case CUDA_ERROR_OUT_OF_MEMORY: + return "CUDA_ERROR_OUT_OF_MEMORY"; + case CUDA_ERROR_NOT_INITIALIZED: + return "CUDA_ERROR_NOT_INITIALIZED"; + case CUDA_ERROR_DEINITIALIZED: + return "CUDA_ERROR_DEINITIALIZED"; + case CUDA_ERROR_PROFILER_DISABLED: + return "CUDA_ERROR_PROFILER_DISABLED"; + case CUDA_ERROR_PROFILER_NOT_INITIALIZED: + return "CUDA_ERROR_PROFILER_NOT_INITIALIZED"; + case CUDA_ERROR_PROFILER_ALREADY_STARTED: + return "CUDA_ERROR_PROFILER_ALREADY_STARTED"; + case CUDA_ERROR_PROFILER_ALREADY_STOPPED: + return "CUDA_ERROR_PROFILER_ALREADY_STOPPED"; + case CUDA_ERROR_NO_DEVICE: + return "CUDA_ERROR_NO_DEVICE"; + case CUDA_ERROR_INVALID_DEVICE: + return "CUDA_ERROR_INVALID_DEVICE"; + case CUDA_ERROR_INVALID_IMAGE: + return "CUDA_ERROR_INVALID_IMAGE"; + case CUDA_ERROR_INVALID_CONTEXT: + return "CUDA_ERROR_INVALID_CONTEXT"; + case CUDA_ERROR_CONTEXT_ALREADY_CURRENT: + return "CUDA_ERROR_CONTEXT_ALREADY_CURRENT"; + case CUDA_ERROR_MAP_FAILED: + return "CUDA_ERROR_MAP_FAILED"; + case CUDA_ERROR_UNMAP_FAILED: + return "CUDA_ERROR_UNMAP_FAILED"; + case CUDA_ERROR_ARRAY_IS_MAPPED: + return "CUDA_ERROR_ARRAY_IS_MAPPED"; + case CUDA_ERROR_ALREADY_MAPPED: + return "CUDA_ERROR_ALREADY_MAPPED"; + case CUDA_ERROR_NO_BINARY_FOR_GPU: + return "CUDA_ERROR_NO_BINARY_FOR_GPU"; + case CUDA_ERROR_ALREADY_ACQUIRED: + return "CUDA_ERROR_ALREADY_ACQUIRED"; + case CUDA_ERROR_NOT_MAPPED: + return "CUDA_ERROR_NOT_MAPPED"; + case CUDA_ERROR_NOT_MAPPED_AS_ARRAY: + return "CUDA_ERROR_NOT_MAPPED_AS_ARRAY"; + case CUDA_ERROR_NOT_MAPPED_AS_POINTER: + return "CUDA_ERROR_NOT_MAPPED_AS_POINTER"; + case CUDA_ERROR_ECC_UNCORRECTABLE: + return "CUDA_ERROR_ECC_UNCORRECTABLE"; + case CUDA_ERROR_UNSUPPORTED_LIMIT: + return "CUDA_ERROR_UNSUPPORTED_LIMIT"; + case CUDA_ERROR_CONTEXT_ALREADY_IN_USE: + return "CUDA_ERROR_CONTEXT_ALREADY_IN_USE"; + case CUDA_ERROR_PEER_ACCESS_UNSUPPORTED: + return "CUDA_ERROR_PEER_ACCESS_UNSUPPORTED"; + case CUDA_ERROR_INVALID_SOURCE: + return "CUDA_ERROR_INVALID_SOURCE"; + case CUDA_ERROR_FILE_NOT_FOUND: + return "CUDA_ERROR_FILE_NOT_FOUND"; + case CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND: + return "CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND"; + case CUDA_ERROR_SHARED_OBJECT_INIT_FAILED: + return "CUDA_ERROR_SHARED_OBJECT_INIT_FAILED"; + case CUDA_ERROR_OPERATING_SYSTEM: + return "CUDA_ERROR_OPERATING_SYSTEM"; + case CUDA_ERROR_INVALID_HANDLE: + return "CUDA_ERROR_INVALID_HANDLE"; + case CUDA_ERROR_NOT_FOUND: + return "CUDA_ERROR_NOT_FOUND"; + case CUDA_ERROR_NOT_READY: + return "CUDA_ERROR_NOT_READY"; + case CUDA_ERROR_LAUNCH_FAILED: + return "CUDA_ERROR_LAUNCH_FAILED"; + case CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES: + return "CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES"; + case CUDA_ERROR_LAUNCH_TIMEOUT: + return "CUDA_ERROR_LAUNCH_TIMEOUT"; + case CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING: + return "CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING"; + case CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED: + return "CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED"; + case CUDA_ERROR_PEER_ACCESS_NOT_ENABLED: + return "CUDA_ERROR_PEER_ACCESS_NOT_ENABLED"; + case CUDA_ERROR_PEER_MEMORY_ALREADY_REGISTERED: + return "CUDA_ERROR_PEER_MEMORY_ALREADY_REGISTERED"; + case CUDA_ERROR_PEER_MEMORY_NOT_REGISTERED: + return "CUDA_ERROR_PEER_MEMORY_NOT_REGISTERED"; + case CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE: + return "CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE"; + case CUDA_ERROR_CONTEXT_IS_DESTROYED: + return "CUDA_ERROR_CONTEXT_IS_DESTROYED"; + case CUDA_ERROR_ASSERT: + return "CUDA_ERROR_ASSERT"; + case CUDA_ERROR_TOO_MANY_PEERS: + return "CUDA_ERROR_TOO_MANY_PEERS"; + case CUDA_ERROR_HOST_MEMORY_ALREADY_REGISTERED: + return "CUDA_ERROR_HOST_MEMORY_ALREADY_REGISTERED"; + case CUDA_ERROR_HOST_MEMORY_NOT_REGISTERED: + return "CUDA_ERROR_HOST_MEMORY_NOT_REGISTERED"; + case CUDA_ERROR_NOT_PERMITTED: + return "CUDA_ERROR_NOT_PERMITTED"; + case CUDA_ERROR_NOT_SUPPORTED: + return "CUDA_ERROR_NOT_SUPPORTED"; + case CUDA_ERROR_UNKNOWN: + return "CUDA_ERROR_UNKNOWN"; + } + return "INVALID CUresult: " + result; + } + + /** + * Private constructor to prevent instantiation. + */ + private CUresult() { + } + +} diff --git a/src/com/sheepit/client/hardware/gpu/GPU.java b/src/com/sheepit/client/hardware/gpu/GPU.java new file mode 100644 index 0000000..fdbf8a4 --- /dev/null +++ b/src/com/sheepit/client/hardware/gpu/GPU.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2013-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.hardware.gpu; + +import java.util.LinkedList; +import java.util.List; + +import com.sheepit.client.os.OS; +import com.sun.jna.Native; +import com.sun.jna.NativeLong; + +public class GPU { + public static List devices = null; + + public static boolean generate() { + OS os = OS.getOS(); + String path = os.getCUDALib(); + if (path == null) { + System.out.println("GPU.listDevices failed to get CUDA lib"); + return false; + } + CUDA cudalib = null; + try { + cudalib = (CUDA) Native.loadLibrary(path, CUDA.class); + } + catch (java.lang.UnsatisfiedLinkError e) { + System.out.println("GPU.listDevices failed to load CUDA lib"); + return false; + } + catch (java.lang.ExceptionInInitializerError e) { + System.out.println("GPU.listDevices ExceptionInInitializerError " + e); + return false; + } + catch (Exception e) { + System.out.println("GPU.listDevices generic exception " + e); + return false; + } + + int result = CUresult.CUDA_ERROR_UNKNOWN; + + result = cudalib.cuInit(0); + if (result != CUresult.CUDA_SUCCESS) { + return false; + } + + if (result == CUresult.CUDA_ERROR_NO_DEVICE) { + System.out.println("NO DEVICE"); + return false; + } + + int[] count = new int[1]; + result = cudalib.cuDeviceGetCount(count); + + if (result != CUresult.CUDA_SUCCESS) { + System.out.println("GPU.listDevices cuDeviceGetCount failed (ret: " + CUresult.stringFor(result) + ")"); + return false; + } + + devices = new LinkedList(); + + for (int num = 0; num < count[0]; num++) { + byte name[] = new byte[256]; + + result = cudalib.cuDeviceGetName(name, 256, num); + if (result != CUresult.CUDA_SUCCESS) { + System.out.println("GPU.listDevices cuDeviceGetName failed (ret: " + CUresult.stringFor(result) + ")"); + continue; + } + + NativeLong[] ram = new NativeLong[1]; + result = cudalib.cuDeviceTotalMem(ram, num); + + if (result != CUresult.CUDA_SUCCESS) { + System.out.println("GPU.listDevices cuDeviceTotalMem failed (ret: " + CUresult.stringFor(result) + ")"); + return false; + } + + devices.add(new GPUDevice(new String(name).trim(), ram[0].longValue(), "CUDA_" + Integer.toString(num))); + } + return true; + } + + public static List listDevices() { + if (devices == null) { + generate(); + } + if (devices == null) { + return null; + } + + List devs = new LinkedList(); + for (GPUDevice dev : devices) { + devs.add(dev.getModel()); + } + return devs; + } + + public static GPUDevice getGPUDevice(String device_model) { + if (device_model == null) { + return null; + } + + if (devices == null) { + generate(); + } + + if (devices == null) { + return null; + } + + for (GPUDevice dev : devices) { + if (device_model.equals(dev.getCudaName()) || device_model.equals(dev.getModel())) { + return dev; + } + } + return null; + } +} diff --git a/src/com/sheepit/client/hardware/gpu/GPUDevice.java b/src/com/sheepit/client/hardware/gpu/GPUDevice.java new file mode 100644 index 0000000..5c78388 --- /dev/null +++ b/src/com/sheepit/client/hardware/gpu/GPUDevice.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.hardware.gpu; + +public class GPUDevice { + private String model; + private long memory; // in B + + private String cudaName; + + public GPUDevice(String model, long ram, String cuda) { + this.model = model; + this.memory = ram; + this.cudaName = cuda; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public long getMemory() { + return memory; + } + + public void setMemory(long memory) { + this.memory = memory; + } + + public String getCudaName() { + return cudaName; + } + + public void setCudaName(String cudaName) { + this.cudaName = cudaName; + } + + @Override + public String toString() { + return "GPUDevice [model=" + model + ", memory=" + memory + ", cudaName=" + cudaName + "]"; + } + +} diff --git a/src/com/sheepit/client/network/ProxyAuthenticator.java b/src/com/sheepit/client/network/ProxyAuthenticator.java new file mode 100644 index 0000000..196cba4 --- /dev/null +++ b/src/com/sheepit/client/network/ProxyAuthenticator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.network; + +import java.net.Authenticator; +import java.net.PasswordAuthentication; + +public class ProxyAuthenticator extends Authenticator { + + private String user; + private String password; + + public ProxyAuthenticator(String user, String password) { + this.user = user; + this.password = password; + } + + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(user, password.toCharArray()); + } +} diff --git a/src/com/sheepit/client/os/Linux.java b/src/com/sheepit/client/os/Linux.java new file mode 100644 index 0000000..cd07cc9 --- /dev/null +++ b/src/com/sheepit/client/os/Linux.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package com.sheepit.client.os; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +import com.sheepit.client.Log; +import com.sheepit.client.Utils; +import com.sheepit.client.hardware.cpu.CPU; + +public class Linux extends OS { + private final String NICE_BINARY_PATH = "nice"; + private Boolean hasNiceBinary; + + public Linux() { + super(); + this.hasNiceBinary = null; + } + + public String name() { + return "linux"; + } + + @Override + public CPU getCPU() { + CPU ret = new CPU(); + try { + String filePath = "/proc/cpuinfo"; + Scanner scanner = new Scanner(new File(filePath)); + + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + if (line.startsWith("model name")) { + String buf[] = line.split(":"); + if (buf.length > 0) { + ret.setName(buf[1].trim()); + } + } + + if (line.startsWith("cpu family")) { + String buf[] = line.split(":"); + if (buf.length > 0) { + ret.setFamily(buf[1].trim()); + } + } + + if (line.startsWith("model") && line.startsWith("model name") == false) { + String buf[] = line.split(":"); + if (buf.length > 0) { + ret.setModel(buf[1].trim()); + } + } + } + scanner.close(); + } + catch (java.lang.NoClassDefFoundError e) { + System.err.println("OS.Linux::getCPU error " + e + " mostly because Scanner class was introducted by Java 5 and you are running are lower version"); + } + catch (Exception e) { + e.printStackTrace(); + } + return ret; + } + + @Override + public int getMemory() { + try { + String filePath = "/proc/meminfo"; + Scanner scanner = new Scanner(new File(filePath)); + + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + + if (line.startsWith("MemTotal")) { + String buf[] = line.split(":"); + if (buf.length > 0) { + Integer buf2 = new Integer(buf[1].trim().split(" ")[0]); + return (((buf2 / 262144) + 1) * 262144); // 256*1024 = 262144 + } + } + } + scanner.close(); + } + catch (java.lang.NoClassDefFoundError e) { + System.err.println("Machine::type error " + e + " mostly because Scanner class was introducted by Java 5 and you are running are lower version"); + } + catch (Exception e) { + e.printStackTrace(); + } + + return 0; + } + + @Override + public String getCUDALib() { + return "cuda"; + } + + @Override + public Process exec(String[] command) throws IOException { + // the renderer have a lib directory so add to the LD_LIBRARY_PATH + // (even if we are not sure that it is the renderer who is launch + + Map new_env = new HashMap(); + new_env.putAll(java.lang.System.getenv()); // clone the env + Boolean has_ld_library_path = new_env.containsKey("LD_LIBRARY_PATH"); + + String lib_dir = (new File(command[0])).getParent() + File.separator + "lib"; + String new_ld_library_path = "/lib:/lib64:/usr/lib:/usr/lib64:/lib/i386-linux-gnu:/lib/x86_64-linux-gnu:/usr/share/local" + ":" + lib_dir; + if (has_ld_library_path == false) { + new_env.put("LD_LIBRARY_PATH", new_ld_library_path); + } + else { + new_env.put("LD_LIBRARY_PATH", new_env.get("LD_LIBRARY_PATH") + ":" + new_ld_library_path); + } + + String[] actual_command = command; + if (this.hasNiceBinary == null) { + this.checkNiceAvailability(); + } + if (this.hasNiceBinary.booleanValue()) { + String[] low = { NICE_BINARY_PATH, "-n", "19" }; // launch the process in lowest priority + actual_command = Utils.concatAll(low, command); + } + else { + Log.getInstance(null).error("No low priority binary, will not launch renderer in normal prioity"); + } + + ProcessBuilder builder = new ProcessBuilder(actual_command); + builder.redirectErrorStream(true); + Map env = builder.environment(); + env.putAll(new_env); + return builder.start(); + } + + protected void checkNiceAvailability() { + ProcessBuilder builder = new ProcessBuilder(); + builder.command(NICE_BINARY_PATH); + builder.redirectErrorStream(true); + Process process = null; + try { + process = builder.start(); + this.hasNiceBinary = true; + } + catch (IOException e) { + this.hasNiceBinary = false; + Log.getInstance(null).error("Failed to find low priority binary, will not launch renderer in normal prioity (" + e + ")"); + } + finally { + if (process != null) { + process.destroy(); + } + } + } +} diff --git a/src/com/sheepit/client/os/Mac.java b/src/com/sheepit/client/os/Mac.java new file mode 100644 index 0000000..7c24b4f --- /dev/null +++ b/src/com/sheepit/client/os/Mac.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package com.sheepit.client.os; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import com.sheepit.client.Log; +import com.sheepit.client.Utils; +import com.sheepit.client.hardware.cpu.CPU; + +public class Mac extends OS { + private final String NICE_BINARY_PATH = "nice"; + private Boolean hasNiceBinary; + + public Mac() { + super(); + this.hasNiceBinary = null; + } + + public String name() { + return "mac"; + } + + @Override + public CPU getCPU() { + CPU ret = new CPU(); + + String command = "sysctl machdep.cpu.family machdep.cpu.brand_string"; + + Process p = null; + BufferedReader input = null; + try { + String line; + p = Runtime.getRuntime().exec(command); + input = new BufferedReader(new InputStreamReader(p.getInputStream())); + + while ((line = input.readLine()) != null) { + String option_cpu_family = "machdep.cpu.family:"; + String option_model_name = "machdep.cpu.brand_string:"; + if (line.startsWith(option_model_name)) { + ret.setName(line.substring(option_model_name.length()).trim()); + } + if (line.startsWith(option_cpu_family)) { + ret.setFamily(line.substring(option_cpu_family.length()).trim()); + } + } + input.close(); + input = null; + } + catch (Exception err) { + System.out.println("exception " + err); + err.printStackTrace(); + ret.setName("Unknown Mac name"); + ret.setFamily("Unknown Mac family"); + } + finally { + if (input != null) { + try { + input.close(); + } + catch (IOException e) { + } + } + + if (p != null) { + p.destroy(); + } + } + + ret.setModel("Unknown"); + + return ret; + } + + @Override + public int getMemory() { + String command = "sysctl hw.memsize"; + + Process p = null; + BufferedReader input = null; + try { + String line; + p = Runtime.getRuntime().exec(command); + input = new BufferedReader(new InputStreamReader(p.getInputStream())); + + while ((line = input.readLine()) != null) { + String option = "hw.memsize:"; + if (line.startsWith(option)) { + String memory = line.substring(option.length()).trim(); // memory in bytes + + return (int) (Long.parseLong(memory) / 1024); + } + } + input.close(); + input = null; + } + catch (Exception err) { + System.out.println("exception " + err); + err.printStackTrace(); + } + finally { + if (input != null) { + try { + input.close(); + } + catch (IOException e) { + } + } + + if (p != null) { + p.destroy(); + } + } + + return -1; + } + + @Override + public Process exec(String[] command) throws IOException { + String[] actual_command = command; + if (this.hasNiceBinary == null) { + this.checkNiceAvailability(); + } + if (this.hasNiceBinary.booleanValue()) { + String[] low = { NICE_BINARY_PATH, "-n", "19" }; // launch the process in lowest priority + actual_command = Utils.concatAll(low, command); + } + else { + Log.getInstance(null).error("No low priority binary, will not launch renderer in normal prioity"); + } + ProcessBuilder builder = new ProcessBuilder(actual_command); + builder.redirectErrorStream(true); + return builder.start(); + } + + @Override + public String getCUDALib() { + return "/usr/local/cuda/lib/libcuda.dylib"; + } + + protected void checkNiceAvailability() { + ProcessBuilder builder = new ProcessBuilder(); + builder.command(NICE_BINARY_PATH); + builder.redirectErrorStream(true); + Process process = null; + try { + process = builder.start(); + this.hasNiceBinary = true; + } + catch (IOException e) { + this.hasNiceBinary = false; + Log.getInstance(null).error("Failed to find low priority binary, will not launch renderer in normal prioity (" + e + ")"); + } + finally { + if (process != null) { + process.destroy(); + } + } + } +} diff --git a/src/com/sheepit/client/os/OS.java b/src/com/sheepit/client/os/OS.java new file mode 100644 index 0000000..c343f5d --- /dev/null +++ b/src/com/sheepit/client/os/OS.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package com.sheepit.client.os; + +import java.io.IOException; + +import com.sheepit.client.hardware.cpu.CPU; + +public abstract class OS { + public String name() { + return "others"; + } + + public abstract CPU getCPU(); + + public abstract int getMemory(); + + public String getCUDALib() { + return null; + } + + public Process exec(String[] command) throws IOException { + ProcessBuilder builder = new ProcessBuilder(command); + builder.redirectErrorStream(true); + return builder.start(); + } + + public boolean kill(Process proc) { + if (proc != null) { + proc.destroy(); + return true; + } + return false; + } + + public static OS getOS() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.indexOf("win") >= 0) { + return new Windows(); + } + else if (os.indexOf("mac") >= 0) { + return new Mac(); + } + else if (os.indexOf("nix") >= 0 || os.indexOf("nux") >= 0) { + return new Linux(); + } + else { + return null; + } + } +} diff --git a/src/com/sheepit/client/os/Windows.java b/src/com/sheepit/client/os/Windows.java new file mode 100644 index 0000000..fd450f8 --- /dev/null +++ b/src/com/sheepit/client/os/Windows.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package com.sheepit.client.os; + +import java.io.IOException; + +import com.sheepit.client.hardware.cpu.CPU; +import com.sheepit.client.os.windows.Kernel32Lib; +import com.sheepit.client.os.windows.WinProcess; +import com.sun.jna.Native; +import com.sun.jna.platform.win32.Advapi32Util; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinBase.MEMORYSTATUSEX; +import com.sun.jna.platform.win32.WinReg; + +public class Windows extends OS { + + public String name() { + return "windows"; + } + + @Override + public CPU getCPU() { + CPU ret = new CPU(); + try { + String[] identifier = java.lang.System.getenv("PROCESSOR_IDENTIFIER").split(" "); + for (int i = 0; i < (identifier.length - 1); i++) { + if (identifier[i].equals("Family")) { + ret.setFamily(new String(identifier[i + 1])); + } + if (identifier[i].equals("Model")) { + ret.setModel(new String(identifier[i + 1])); + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + + try { + final String cpuRegistryRoot = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor"; + String[] processorIds = Advapi32Util.registryGetKeys(WinReg.HKEY_LOCAL_MACHINE, cpuRegistryRoot); + if (processorIds.length > 0) { + String processorId = processorIds[0]; + String cpuRegistryPath = cpuRegistryRoot + "\\" + processorId; + ret.setName(Advapi32Util.registryGetStringValue(WinReg.HKEY_LOCAL_MACHINE, cpuRegistryPath, "ProcessorNameString").trim()); + } + } + catch (Exception e) { + e.printStackTrace(); + } + + // override the arch + String env_arch = java.lang.System.getenv("PROCESSOR_ARCHITEW6432"); + if (env_arch == null || env_arch.compareTo("") == 0) { + env_arch = java.lang.System.getenv("PROCESSOR_ARCHITECTURE"); + } + if (env_arch.compareTo("AMD64") == 0) { + ret.setArch("64bit"); + } + else { + ret.setArch("32bit"); + } + + return ret; + } + + @Override + public int getMemory() { + try { + MEMORYSTATUSEX _memory = new MEMORYSTATUSEX(); + if (Kernel32.INSTANCE.GlobalMemoryStatusEx(_memory)) { + return (int) (_memory.ullTotalPhys.longValue() / 1024); // size in KB + } + } + catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + + @Override + public String getCUDALib() { + return "nvcuda"; + } + + @Override + public Process exec(String[] command) throws IOException { + // disable a popup because the renderer might crash (seg fault) + Kernel32Lib kernel32lib = null; + try { + kernel32lib = (Kernel32Lib) Native.loadLibrary(Kernel32Lib.path, Kernel32Lib.class); + kernel32lib.SetErrorMode(Kernel32Lib.SEM_NOGPFAULTERRORBOX); + } + catch (java.lang.UnsatisfiedLinkError e) { + System.out.println("OS.Windows::exec failed to load kernel32lib " + e); + } + catch (java.lang.ExceptionInInitializerError e) { + System.out.println("OS.Windows::exec failed to load kernel32lib " + e); + } + catch (Exception e) { + System.out.println("OS.Windows::exec failed to load kernel32lib " + e); + } + + ProcessBuilder builder = new ProcessBuilder(command); + builder.redirectErrorStream(true); + Process p = builder.start(); + WinProcess wproc = new WinProcess(p); + wproc.setPriority(WinProcess.PRIORITY_BELOW_NORMAL); + return p; + } + + @Override + public boolean kill(Process process) { + if (process != null) { + WinProcess wproc = new WinProcess(process); + wproc.kill(); + return true; + } + return false; + } +} diff --git a/src/com/sheepit/client/os/windows/Kernel32Lib.java b/src/com/sheepit/client/os/windows/Kernel32Lib.java new file mode 100644 index 0000000..56a4d2b --- /dev/null +++ b/src/com/sheepit/client/os/windows/Kernel32Lib.java @@ -0,0 +1,220 @@ +/* This file was originally taken from JNA project (https://github.com/twall/jna) + * filename: contrib/platform/src/com/sun/jna/platform/win32/Tlhelp32.java + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + */ + +package com.sheepit.client.os.windows; + +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.Library; +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.platform.win32.BaseTSD; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.platform.win32.WinDef.DWORD; +import com.sun.jna.platform.win32.WinNT; +import com.sun.jna.platform.win32.WinNT.HANDLE; + +public interface Kernel32Lib extends Library { + public static final String path = "kernel32"; + + /** + * Includes all heaps of the process specified in th32ProcessID in the snapshot. To enumerate the heaps, see + * Heap32ListFirst. + */ + WinDef.DWORD TH32CS_SNAPHEAPLIST = new WinDef.DWORD(0x00000001); + + /** + * Includes all processes in the system in the snapshot. To enumerate the processes, see Process32First. + */ + WinDef.DWORD TH32CS_SNAPPROCESS = new WinDef.DWORD(0x00000002); + + /** + * Includes all threads in the system in the snapshot. To enumerate the threads, see Thread32First. + */ + WinDef.DWORD TH32CS_SNAPTHREAD = new WinDef.DWORD(0x00000004); + + /** + * Includes all modules of the process specified in th32ProcessID in the snapshot. To enumerate the modules, see + * Module32First. If the function fails with ERROR_BAD_LENGTH, retry the function until it succeeds. + */ + WinDef.DWORD TH32CS_SNAPMODULE = new WinDef.DWORD(0x00000008); + + /** + * Includes all 32-bit modules of the process specified in th32ProcessID in the snapshot when called from a 64-bit + * process. This flag can be combined with TH32CS_SNAPMODULE or TH32CS_SNAPALL. If the function fails with + * ERROR_BAD_LENGTH, retry the function until it succeeds. + */ + WinDef.DWORD TH32CS_SNAPMODULE32 = new WinDef.DWORD(0x00000010); + + /** + * Includes all processes and threads in the system, plus the heaps and modules of the process specified in th32ProcessID. + */ + WinDef.DWORD TH32CS_SNAPALL = new WinDef.DWORD((TH32CS_SNAPHEAPLIST.intValue() | TH32CS_SNAPPROCESS.intValue() | TH32CS_SNAPTHREAD.intValue() | TH32CS_SNAPMODULE.intValue())); + + /** + * Indicates that the snapshot handle is to be inheritable. + */ + WinDef.DWORD TH32CS_INHERIT = new WinDef.DWORD(0x80000000); + + /** + * The system does not display the Windows Error Reporting dialog. + * See: http://msdn.microsoft.com/en-us/library/ms680621%28VS.85%29.aspx + */ + WinDef.DWORD SEM_NOGPFAULTERRORBOX = new WinDef.DWORD(0x0002); + + /** + * Describes an entry from a list of the processes residing in the system address space when a snapshot was taken. + */ + public static class PROCESSENTRY32 extends Structure { + + public static class ByReference extends PROCESSENTRY32 implements Structure.ByReference { + public ByReference() { + } + + public ByReference(Pointer memory) { + super(memory); + } + } + + public PROCESSENTRY32() { + dwSize = new WinDef.DWORD(size()); + } + + public PROCESSENTRY32(Pointer memory) { + useMemory(memory); + read(); + } + + /** + * The size of the structure, in bytes. Before calling the Process32First function, set this member to + * sizeof(PROCESSENTRY32). If you do not initialize dwSize, Process32First fails. + */ + public WinDef.DWORD dwSize; + + /** + * This member is no longer used and is always set to zero. + */ + public WinDef.DWORD cntUsage; + + /** + * The process identifier. + */ + public WinDef.DWORD th32ProcessID; + + /** + * This member is no longer used and is always set to zero. + */ + public BaseTSD.ULONG_PTR th32DefaultHeapID; + + /** + * This member is no longer used and is always set to zero. + */ + public WinDef.DWORD th32ModuleID; + + /** + * The number of execution threads started by the process. + */ + public WinDef.DWORD cntThreads; + + /** + * The identifier of the process that created this process (its parent process). + */ + public WinDef.DWORD th32ParentProcessID; + + /** + * The base priority of any threads created by this process. + */ + public WinDef.LONG pcPriClassBase; + + /** + * This member is no longer used, and is always set to zero. + */ + public WinDef.DWORD dwFlags; + + /** + * The name of the executable file for the process. To retrieve the full path to the executable file, call the + * Module32First function and check the szExePath member of the MODULEENTRY32 structure that is returned. + * However, if the calling process is a 32-bit process, you must call the QueryFullProcessImageName function to + * retrieve the full path of the executable file for a 64-bit process. + */ + public char[] szExeFile = new char[WinDef.MAX_PATH]; + + @Override + protected List getFieldOrder() { + return Arrays.asList(new String[] { "dwSize", "cntUsage", "th32ProcessID", "th32DefaultHeapID", "th32ModuleID", "cntThreads", "th32ParentProcessID", "pcPriClassBase", "dwFlags", "szExeFile" }); + } + } + + /** + * Takes a snapshot of the specified processes, as well as the heaps, modules, and threads used by these processes. + * + * @param dwFlags + * The portions of the system to be included in the snapshot. + * + * @param th32ProcessID + * The process identifier of the process to be included in the snapshot. This parameter can be zero to indicate + * the current process. This parameter is used when the TH32CS_SNAPHEAPLIST, TH32CS_SNAPMODULE, + * TH32CS_SNAPMODULE32, or TH32CS_SNAPALL value is specified. Otherwise, it is ignored and all processes are + * included in the snapshot. + * + * If the specified process is the Idle process or one of the CSRSS processes, this function fails and the last + * error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from opening them. + * + * If the specified process is a 64-bit process and the caller is a 32-bit process, this function fails and the + * last error code is ERROR_PARTIAL_COPY (299). + * + * @return + * If the function succeeds, it returns an open handle to the specified snapshot. + * + * If the function fails, it returns INVALID_HANDLE_VALUE. To get extended error information, call GetLastError. + * Possible error codes include ERROR_BAD_LENGTH. + */ + public WinNT.HANDLE CreateToolhelp32Snapshot(WinDef.DWORD dwFlags, WinDef.DWORD th32ProcessID); + + /** + * Retrieves information about the first process encountered in a system snapshot. + * + * @param hSnapshot A handle to the snapshot returned from a previous call to the CreateToolhelp32Snapshot function. + * @param lppe A pointer to a PROCESSENTRY32 structure. It contains process information such as the name of the + * executable file, the process identifier, and the process identifier of the parent process. + * @return + * Returns TRUE if the first entry of the process list has been copied to the buffer or FALSE otherwise. The + * ERROR_NO_MORE_FILES error value is returned by the GetLastError function if no processes exist or the snapshot + * does not contain process information. + */ + public boolean Process32First(WinNT.HANDLE hSnapshot, Kernel32Lib.PROCESSENTRY32.ByReference lppe); + + /** + * Retrieves information about the next process recorded in a system snapshot. + * + * @param hSnapshot A handle to the snapshot returned from a previous call to the CreateToolhelp32Snapshot function. + * @param lppe A pointer to a PROCESSENTRY32 structure. + * @return + * Returns TRUE if the next entry of the process list has been copied to the buffer or FALSE otherwise. The + * ERROR_NO_MORE_FILES error value is returned by the GetLastError function if no processes exist or the snapshot + * does not contain process information. + */ + public boolean Process32Next(WinNT.HANDLE hSnapshot, Kernel32Lib.PROCESSENTRY32.ByReference lppe); + + public boolean SetPriorityClass(HANDLE hProcess, int dwPriorityClass); + + /** + * Controls whether the system will handle the specified types of serious errors or whether the process will handle them. + * See: http://msdn.microsoft.com/en-us/library/ms680621%28VS.85%29.aspx + * @param uMode The process error mode. This parameter can be one or more of the following values. + */ + public int SetErrorMode(DWORD uMode); + +} diff --git a/src/com/sheepit/client/os/windows/WinProcess.java b/src/com/sheepit/client/os/windows/WinProcess.java new file mode 100644 index 0000000..0b3c6fa --- /dev/null +++ b/src/com/sheepit/client/os/windows/WinProcess.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2013 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.os.windows; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.Kernel32Util; +import com.sun.jna.platform.win32.WinDef.DWORD; +import com.sun.jna.platform.win32.WinNT; + +public class WinProcess { + public static final int PRIORITY_IDLE = 0x40; + public static final int PRIORITY_BELOW_NORMAL = 0x4000; + public static final int PRIORITY_NORMAL = 0x20; + public static final int PRIORITY_ABOVE_NORMAL = 0x8000; + public static final int PRIORITY_HIGH = 0x80; + public static final int PRIORITY_REALTIME = 0x100; + + private WinNT.HANDLE handle; + private int pid; + Kernel32Lib kernel32lib; + + public WinProcess() { + this.handle = null; + this.pid = -1; + this.kernel32lib = null; + try { + this.kernel32lib = (Kernel32Lib) Native.loadLibrary(Kernel32Lib.path, Kernel32Lib.class); + } + catch (java.lang.UnsatisfiedLinkError e) { + System.out.println("WinProcess::construct " + e); + } + catch (java.lang.ExceptionInInitializerError e) { + System.out.println("WinProcess::construct " + e); + } + catch (Exception e) { + System.out.println("WinProcess::construct " + e); + } + } + + public WinProcess(Process process) { + this(); + try { + Field f = process.getClass().getDeclaredField("handle"); + f.setAccessible(true); + long val = f.getLong(process); + this.handle = new WinNT.HANDLE(); + this.handle.setPointer(Pointer.createConstant(val)); + this.pid = Kernel32.INSTANCE.GetProcessId(this.handle); + } + catch (NoSuchFieldException e) { + } + catch (IllegalArgumentException e) { + } + catch (IllegalAccessException e) { + } + } + + public WinProcess(int pid_) throws IOException { + this.handle = Kernel32.INSTANCE.OpenProcess(0x0400 | // PROCESS_QUERY_INFORMATION + 0x0800 | // PROCESS_SUSPEND_RESUME + 0x0001 | // PROCESS_TERMINATE + 0x0200 | // PROCESS_SET_INFORMATION + 0x00100000, // SYNCHRONIZE + false, pid); + if (this.handle == null) { + throw new IOException("OpenProcess failed: " + Kernel32Util.formatMessageFromLastErrorCode(Kernel32.INSTANCE.GetLastError()) + " (pid: " + pid_ + ")"); + } + this.pid = pid_; + } + + @Override + protected void finalize() throws Throwable { + if (this.handle != null) { + Kernel32.INSTANCE.CloseHandle(this.handle); + this.handle = null; + } + this.pid = -1; + } + + public boolean kill() { + try { + List children = this.getChildren(); + this.terminate(); + for (WinProcess child : children) { + child.kill(); + } + } + catch (IOException e) { + e.printStackTrace(); + } + + return false; + } + + public boolean setPriority(int priority) { + return this.kernel32lib.SetPriorityClass(this.handle, priority); + } + + private void terminate() { + Kernel32.INSTANCE.TerminateProcess(this.handle, 0); + } + + private List getChildren() throws IOException { + ArrayList result = new ArrayList(); + + WinNT.HANDLE hSnap = this.kernel32lib.CreateToolhelp32Snapshot(Kernel32Lib.TH32CS_SNAPPROCESS, new DWORD(0)); + Kernel32Lib.PROCESSENTRY32.ByReference ent = new Kernel32Lib.PROCESSENTRY32.ByReference(); + if (!this.kernel32lib.Process32First(hSnap, ent)) { + return result; + } + do { + if (ent.th32ParentProcessID.intValue() == this.pid) { + try { + result.add(new WinProcess(ent.th32ProcessID.intValue())); + } + catch (IOException e) { + System.err.println("WinProcess::getChildren, IOException " + e); + } + } + } + while (this.kernel32lib.Process32Next(hSnap, ent)); + + Kernel32.INSTANCE.CloseHandle(hSnap); + + return result; + } + + public String toString() { + return "WinProcess(pid: " + this.pid + ", handle " + this.handle + ")"; + } +} diff --git a/src/com/sheepit/client/standalone/GuiText.java b/src/com/sheepit/client/standalone/GuiText.java new file mode 100644 index 0000000..73fe442 --- /dev/null +++ b/src/com/sheepit/client/standalone/GuiText.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.standalone; + +import com.sheepit.client.Gui; +import com.sheepit.client.Log; + +public class GuiText implements Gui { + private int framesRendered; + private Log log; + + public GuiText() { + this.framesRendered = 0; + this.log = Log.getInstance(null); + } + + @Override + public void start() { + } + + @Override + public void stop() { + } + + @Override + public void status(String msg_) { + System.out.println(msg_); + log.debug("GUI " + msg_); + } + + @Override + public void error(String err_) { + System.out.println("Error " + err_); + log.error("Error " + err_); + } + + @Override + public void AddFrameRendered() { + this.framesRendered += 1; + System.out.println("frame rendered: " + this.framesRendered); + + } + + @Override + public void framesRemaining(int n_) { + System.out.println("frame remaining: " + n_); + } + +} diff --git a/src/com/sheepit/client/standalone/Worker.java b/src/com/sheepit/client/standalone/Worker.java new file mode 100644 index 0000000..1e10a36 --- /dev/null +++ b/src/com/sheepit/client/standalone/Worker.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2010-2014 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client.standalone; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import static org.kohsuke.args4j.ExampleMode.REQUIRED; +import org.kohsuke.args4j.Option; + +import java.io.File; +import java.net.Authenticator; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.LinkedList; +import com.sheepit.client.Client; +import com.sheepit.client.Configuration; +import com.sheepit.client.Configuration.ComputeType; +import com.sheepit.client.Gui; +import com.sheepit.client.Log; +import com.sheepit.client.Pair; +import com.sheepit.client.ShutdownHook; +import com.sheepit.client.hardware.gpu.GPU; +import com.sheepit.client.hardware.gpu.GPUDevice; +import com.sheepit.client.network.ProxyAuthenticator; + +public class Worker { + @Option(name = "-server", usage = "Render-farm server, default https://www.sheepit-renderfarm.com", metaVar = "URL", required = false) + private String server = "https://www.sheepit-renderfarm.com"; + + @Option(name = "-login", usage = "User's login", metaVar = "LOGIN", required = true) + private String login = ""; + + @Option(name = "-password", usage = "User's password", metaVar = "PASSWORD", required = true) + private String password = ""; + + @Option(name = "-cache-dir", usage = "Cache/Working directory. Caution, everything in it not related to the render-farm will be removed", metaVar = "/tmp/cache", required = false) + private String cache_dir = null; + + @Option(name = "-max-uploading-job", usage = "", metaVar = "1", required = false) + private int max_upload = -1; + + @Option(name = "-gpu", usage = "CUDA name of the GPU used for the render, for example CUDA_0", metaVar = "CUDA_0", required = false) + private String gpu_device = null; + + @Option(name = "-compute-method", usage = "CPU: only use cpu, GPU: only use gpu, CPU_GPU: can use cpu and gpu (not at the same time) if -gpu is not use it will not use the gpu", metaVar = "CPU_GPU", required = false) + private String method = null; + + @Option(name = "-cores", usage = "Number of core/thread to use for the render", metaVar = "3", required = false) + private int nb_cores = -1; + + @Option(name = "--verbose", usage = "Display log", required = false) + private boolean print_log = false; + + @Option(name = "-request-time", usage = "H1:M1-H2:M2,H3:M3-H4:M4 Use the 24h format. For example to request job between 2am-8.30am and 5pm-11pm you should do --request-time 2:00-8:30,17:00-23:00 Caution, it's the requesting job time to get a project not the working time", metaVar = "2:00-8:30,17:00-23:00", required = false) + private String request_time = null; + + @Option(name = "-proxy", usage = "URL of the proxy", metaVar = "http://login:password@host:port", required = false) + private String proxy = null; + + @Option(name = "-extras", usage = "Extras data push on the authentication request", required = false) + private String extras = null; + + @Option(name = "--version", usage = "Display application version", required = false) + private boolean display_version = false; + + public static void main(String[] args) { + new Worker().doMain(args); + } + + public void doMain(String[] args) { + CmdLineParser parser = new CmdLineParser(this); + try { + parser.parseArgument(args); + } + catch (CmdLineException e) { + System.err.println(e.getMessage()); + System.err.println("Usage: "); + parser.printUsage(System.err); + System.err.println(); + System.err.println("Example: java " + this.getClass().getName() + " " + parser.printExample(REQUIRED)); + return; + } + + if (display_version) { + Configuration config = new Configuration(null, "", ""); + System.out.println("Version: " + config.getJarVersion()); + return; + } + + ComputeType compute_method = ComputeType.CPU_GPU; + Configuration config = new Configuration(null, login, password); + config.setPrintLog(print_log); + + if (cache_dir != null) { + File a_dir = new File(cache_dir); + if (a_dir.isDirectory() && a_dir.canWrite()) { + config.setCacheDir(a_dir); + } + } + + if (max_upload != -1) { + if (max_upload <= 0) { + System.err.println("Error: max upload should be a greater than zero"); + return; + } + config.setMaxUploadingJob(max_upload); + } + + if (gpu_device != null) { + String cuda_str = "CUDA_"; + if (gpu_device.startsWith(cuda_str) == false) { + System.err.println("CUDA_DEVICE should look like 'CUDA_X' where X is a number"); + return; + } + try { + Integer.parseInt(gpu_device.substring(cuda_str.length())); + } + catch (NumberFormatException en) { + System.err.println("CUDA_DEVICE should look like 'CUDA_X' where X is a number"); + return; + } + GPUDevice gpu = GPU.getGPUDevice(gpu_device); + if (gpu == null) { + System.err.println("GPU unknown"); + System.exit(2); + } + config.setUseGPU(gpu); + } + + if (request_time != null) { + String[] intervals = request_time.split(","); + if (intervals != null) { + config.requestTime = new LinkedList>(); + + SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm"); + for (String interval : intervals) { + String[] times = interval.split("-"); + if (times != null && times.length == 2) { + Calendar start = Calendar.getInstance(); + Calendar end = Calendar.getInstance(); + + try { + start.setTime(timeFormat.parse(times[0])); + end.setTime(timeFormat.parse(times[1])); + } + catch (ParseException e) { + System.err.println("Error: wrong format in request time"); + System.exit(2); + } + + if (start.before(end)) { + config.requestTime.add(new Pair(start, end)); + } + else { + System.err.println("Error: wrong request time " + times[0] + " is after " + times[1]); + System.exit(2); + } + } + } + } + } + + if (nb_cores < -1) { + System.err.println("Error: use-number-core should be a greater than zero"); + return; + } + else { + config.setUseNbCores(nb_cores); + } + + if (method != null) { + if (method.equalsIgnoreCase("cpu")) { + compute_method = ComputeType.CPU_ONLY; + } + else if (method.equalsIgnoreCase("gpu")) { + compute_method = ComputeType.GPU_ONLY; + } + else if (method.equalsIgnoreCase("cpu_gpu") || method.equalsIgnoreCase("gpu_cpu")) { + compute_method = ComputeType.CPU_GPU; + } + else { + System.err.println("Error: compute-method unknown"); + System.exit(2); + } + } + + if (proxy != null) { + try { + URL url = new URL(proxy); + String userinfo = url.getUserInfo(); + if (userinfo != null) { + String[] elements = userinfo.split(":"); + if (elements.length == 2) { + String proxy_user = elements[0]; + String proxy_password = elements[1]; + + if (proxy_user != null && proxy_password != null) { + Authenticator.setDefault(new ProxyAuthenticator(proxy_user, proxy_password)); + } + } + } + + System.setProperty("http.proxyHost", url.getHost()); + System.setProperty("http.proxyPort", Integer.toString(url.getPort())); + + System.setProperty("https.proxyHost", url.getHost()); + System.setProperty("https.proxyPort", Integer.toString(url.getPort())); + } + catch (MalformedURLException e) { + System.err.println("Error: wrong url for proxy"); + System.err.println(e); + System.exit(2); + } + } + + if (extras != null) { + config.setExtras(extras); + } + + if (compute_method == ComputeType.CPU_ONLY) { // the client was to render with cpu but on the server side project type are cpu+gpu or gpu prefered but never cpu only + compute_method = ComputeType.CPU_GPU; + config.setComputeMethod(compute_method); + config.setUseGPU(null); // remove the GPU + } + else { + config.setComputeMethod(compute_method); // doing it here because it have to be done after the setUseGPU + } + + Log.getInstance(config).debug("client version " + config.getJarVersion()); + + Gui gui = new GuiText(); + Client cli = new Client(gui, config, server); + + ShutdownHook hook = new ShutdownHook(cli); + hook.attachShutDownHook(); + + cli.run(); + cli.stop(); + } +}