diff --git a/src/com/sheepit/client/Client.java b/src/com/sheepit/client/Client.java index 5fc25474..134e75d3 100644 --- a/src/com/sheepit/client/Client.java +++ b/src/com/sheepit/client/Client.java @@ -647,7 +647,7 @@ protected void sendError(int step_, Job job_to_reset_, Error.Type error) { args += "&extras=" + job_to_reset_.getExtras(); } } - this.server.HTTPSendFile(this.server.getPage("error") + args, temp_file.getAbsolutePath(), step_); + this.server.HTTPSendFile(this.server.getPage("error") + args, temp_file.getAbsolutePath(), step_, this.gui); temp_file.delete(); } catch (Exception e) { @@ -938,7 +938,7 @@ protected Error.Type confirmJob(Job ajob, int checkpoint) { Type confirmJobReturnCode = Error.Type.OK; retryLoop: while (nb_try < max_try && ret != ServerCode.OK) { - ret = this.server.HTTPSendFile(url_real, ajob.getOutputImagePath(), checkpoint); + ret = this.server.HTTPSendFile(url_real, ajob.getOutputImagePath(), checkpoint, this.gui); switch (ret) { case OK: // no issue, exit the loop diff --git a/src/com/sheepit/client/Gui.java b/src/com/sheepit/client/Gui.java index 71df53d5..2bf97296 100644 --- a/src/com/sheepit/client/Gui.java +++ b/src/com/sheepit/client/Gui.java @@ -40,6 +40,8 @@ public interface Gui { public void setRenderingTime(String time_); + public void displayTransferStats(TransferStats downloads, TransferStats uploads); + public void displayStats(Stats stats); public void displayUploadQueueStats(int queueSize, long queueVolume); diff --git a/src/com/sheepit/client/Server.java b/src/com/sheepit/client/Server.java index 222d570e..41d0172c 100644 --- a/src/com/sheepit/client/Server.java +++ b/src/com/sheepit/client/Server.java @@ -28,6 +28,8 @@ import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.*; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -83,6 +85,9 @@ public class Server extends Thread { private long lastRequestTime; private int keepmealive_duration; // time in ms + private TransferStats dlStats = new TransferStats(); + private TransferStats ulStats = new TransferStats(); + public Server(String url_, Configuration user_config_, Client client_) { super(); this.base_url = url_; @@ -427,7 +432,6 @@ public Error.Type HTTPGetFile(String url_, String destination_, Gui gui_, String return Error.Type.DOWNLOAD_FILE; } - long start = new Date().getTime(); is = response.body().byteStream(); output = new FileOutputStream(destination_); @@ -437,6 +441,8 @@ public Error.Type HTTPGetFile(String url_, String destination_, Gui gui_, String long written = 0; long lastUpd = 0; // last GUI progress update + LocalDateTime startRequestTime = LocalDateTime.now(); + while ((len = is.read(buffer)) != -1) { if (this.client.getRenderingJob().isServerBlockJob()) { return Error.Type.RENDERER_KILLED_BY_SERVER; @@ -454,10 +460,13 @@ else if (this.client.getRenderingJob().isUserBlockJob()) { } } + LocalDateTime endRequestTime = LocalDateTime.now(); + Duration duration = Duration.between(startRequestTime, endRequestTime); + this.dlStats.calc(written, ((duration.getSeconds() * 1000) + (duration.getNano() / 1000000))); + gui_.displayTransferStats(dlStats, ulStats); gui_.status(status_, 100, size); - long end = new Date().getTime(); - this.log.debug(String.format("File downloaded at %.1f kB/s, written %d B", ((float) (size / 1000)) / ((float) (end - start) / 1000), written)); + this.log.debug(String.format("File downloaded at %.1f KB/s, written %d bytes", (float) (size/1024) / duration.getSeconds(), written)); this.lastRequestTime = new Date().getTime(); return Error.Type.OK; @@ -491,22 +500,33 @@ else if (this.client.getRenderingJob().isUserBlockJob()) { return Error.Type.DOWNLOAD_FILE; } - public ServerCode HTTPSendFile(String surl, String file1, int checkpoint) { + public ServerCode HTTPSendFile(String surl, String file1, int checkpoint, Gui gui) { this.log.debug(checkpoint, "Server::HTTPSendFile(" + surl + "," + file1 + ")"); try { String fileMimeType = Utils.findMimeType(file1); + File fileHandler = new File(file1); MediaType MEDIA_TYPE = MediaType.parse(fileMimeType); // e.g. "image/png" RequestBody uploadContent = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("file", new File(file1).getName(), RequestBody.create(new File(file1), MEDIA_TYPE)).build(); + .addFormDataPart("file", fileHandler.getName(), RequestBody.create(fileHandler, MEDIA_TYPE)).build(); Request request = new Request.Builder().addHeader("User-Agent", HTTP_USER_AGENT).url(surl).post(uploadContent).build(); + LocalDateTime startRequestTime = LocalDateTime.now(); + Call call = httpClient.newCall(request); Response response = call.execute(); + LocalDateTime endRequestTime = LocalDateTime.now(); + Duration duration = Duration.between(startRequestTime, endRequestTime); + + this.ulStats.calc(fileHandler.length(), ((duration.getSeconds() * 1000) + (duration.getNano() / 1000000))); + gui.displayTransferStats(dlStats, ulStats); + + this.log.debug(String.format("File uploaded at %s/s, uploaded %d bytes", Utils.formatDataConsumption((long) fileHandler.length() / duration.getSeconds()), fileHandler.length())); + int r = response.code(); String contentType = response.body().contentType().toString(); diff --git a/src/com/sheepit/client/TransferStats.java b/src/com/sheepit/client/TransferStats.java new file mode 100644 index 00000000..3c515d42 --- /dev/null +++ b/src/com/sheepit/client/TransferStats.java @@ -0,0 +1,36 @@ +package com.sheepit.client; + +import lombok.AllArgsConstructor; + +/**************** + * Holds the session traffic statistics. The constructor accepts two parameters: + * @long bytes - bytes transferred in the session + * @Job seconds - seconds spent transferring the data + */ +@AllArgsConstructor +public class TransferStats { + private long bytes; + private long millis; + + public TransferStats() { + this.bytes = 0; + this.millis = 0; + } + + public void calc(long bytes, long millis) { + this.bytes += bytes; + this.millis += millis; + } + + public String getSessionTraffic() { + return Utils.formatDataConsumption(this.bytes); + } + + public String getAverageSessionSpeed() { + try { + return Utils.formatDataConsumption((long) (this.bytes / (this.millis / 1000f))); + } catch (ArithmeticException e) { // Unlikely, but potential division by zero fallback if first transfer is done in zero millis + return Utils.formatDataConsumption((long) (this.bytes / (0.1f))); + } + } +} diff --git a/src/com/sheepit/client/Utils.java b/src/com/sheepit/client/Utils.java index 2d7daeeb..fbead751 100644 --- a/src/com/sheepit/client/Utils.java +++ b/src/com/sheepit/client/Utils.java @@ -228,4 +228,24 @@ public static String findMimeType(String file) throws IOException { return mimeType; } + + public static String formatDataConsumption(long bytes) { + float divider = 0; + String suffix = ""; + + if (bytes > 1099511627776f) { // 1TB + divider = 1099511627776f; + suffix = "TB"; + } + else if (bytes > 1073741824) { // 1GB + divider = 1073741824; + suffix = "GB"; + } + else { // 1MB + divider = 1048576; + suffix = "MB"; + } + + return String.format("%.2f%s", (bytes / divider), suffix); + } } diff --git a/src/com/sheepit/client/standalone/GuiSwing.java b/src/com/sheepit/client/standalone/GuiSwing.java index b4e15ce3..bc447721 100644 --- a/src/com/sheepit/client/standalone/GuiSwing.java +++ b/src/com/sheepit/client/standalone/GuiSwing.java @@ -50,6 +50,7 @@ import com.sheepit.client.Gui; import com.sheepit.client.SettingsLoader; import com.sheepit.client.Stats; +import com.sheepit.client.TransferStats; import com.sheepit.client.standalone.swing.activity.Settings; import com.sheepit.client.standalone.swing.activity.Working; import lombok.Getter; @@ -223,6 +224,10 @@ else if (client.getConfiguration().getTheme().equals("dark")) { } } + @Override public synchronized void displayTransferStats(TransferStats downloads, TransferStats uploads) { + this.activityWorking.displayTransferStats(downloads, uploads); + } + @Override public void AddFrameRendered() { framesRendered++; diff --git a/src/com/sheepit/client/standalone/GuiText.java b/src/com/sheepit/client/standalone/GuiText.java index d65a8c8c..87974c88 100644 --- a/src/com/sheepit/client/standalone/GuiText.java +++ b/src/com/sheepit/client/standalone/GuiText.java @@ -23,6 +23,7 @@ import com.sheepit.client.Gui; import com.sheepit.client.Log; import com.sheepit.client.Stats; +import com.sheepit.client.TransferStats; import com.sheepit.client.standalone.text.CLIInputActionHandler; import com.sheepit.client.standalone.text.CLIInputObserver; @@ -132,6 +133,12 @@ else if (client.isRunning() && client.isSuspended() == false) { System.out.println(String.format("%s Frames rendered: %d", this.df.format(new Date()), this.framesRendered)); } + @Override public synchronized void displayTransferStats(TransferStats downloads, TransferStats uploads) { + System.out.println(String + .format("%s Session downloads: %s @ %s/s / Uploads: %s @ %s/s", this.df.format(new Date()), downloads.getSessionTraffic(), + downloads.getAverageSessionSpeed(), uploads.getSessionTraffic(), uploads.getAverageSessionSpeed())); + } + @Override public void displayStats(Stats stats) { System.out.println(String.format("%s Frames remaining: %d", this.df.format(new Date()), stats.getRemainingFrame())); System.out.println(String.format("%s Credits earned: %d", this.df.format(new Date()), stats.getCreditsEarnedDuringSession())); diff --git a/src/com/sheepit/client/standalone/GuiTextOneLine.java b/src/com/sheepit/client/standalone/GuiTextOneLine.java index 0c6e8d2d..271a35c6 100644 --- a/src/com/sheepit/client/standalone/GuiTextOneLine.java +++ b/src/com/sheepit/client/standalone/GuiTextOneLine.java @@ -22,6 +22,7 @@ import com.sheepit.client.Client; import com.sheepit.client.Gui; import com.sheepit.client.Stats; +import com.sheepit.client.TransferStats; import com.sheepit.client.standalone.text.CLIInputActionHandler; import com.sheepit.client.standalone.text.CLIInputObserver; @@ -154,6 +155,10 @@ else if (client.isRunning() && client.isSuspended() == false) { updateLine(); } + @Override public synchronized void displayTransferStats(TransferStats downloads, TransferStats uploads) { + // Session traffic stats not shown in the 1 line UI to avoid increasing the line length above 120 chars + } + @Override public void displayStats(Stats stats) { remaining = stats.getRemainingFrame(); creditsEarned = String.valueOf(stats.getCreditsEarnedDuringSession()); diff --git a/src/com/sheepit/client/standalone/swing/activity/Working.java b/src/com/sheepit/client/standalone/swing/activity/Working.java index dea04405..64dc3672 100644 --- a/src/com/sheepit/client/standalone/swing/activity/Working.java +++ b/src/com/sheepit/client/standalone/swing/activity/Working.java @@ -49,6 +49,7 @@ import com.sheepit.client.Job; import com.sheepit.client.Log; import com.sheepit.client.Stats; +import com.sheepit.client.TransferStats; import com.sheepit.client.Utils; import com.sheepit.client.standalone.GuiSwing; import com.sheepit.client.standalone.GuiSwing.ActivityType; @@ -75,6 +76,8 @@ public class Working implements Activity { private JLabel connected_machines_value; private JLabel user_info_total_rendertime_this_session_value; private JLabel userInfoQueuedUploadsAndSizeValue; + private JLabel sessionDownloadsStatsValue; + private JLabel sessionUploadsStatsValue; private String currentTheme; private Log log; @@ -97,6 +100,8 @@ public Working(GuiSwing parent_) { lastRenderTime = new JLabel(""); lastRender = new JLabel(""); userInfoQueuedUploadsAndSizeValue = new JLabel("0"); + sessionDownloadsStatsValue = new JLabel("0KB"); + sessionUploadsStatsValue = new JLabel("0KB"); currentTheme = UIManager.getLookAndFeel().getName(); // Capture the theme on component instantiation previousStatus = ""; log = Log.getInstance(parent_.getConfiguration()); @@ -126,6 +131,8 @@ public Working(GuiSwing parent_) { lastRenderTime = new JLabel(lastRenderTime.getText()); lastRender = new JLabel(lastRender.getText()); userInfoQueuedUploadsAndSizeValue = new JLabel(userInfoQueuedUploadsAndSizeValue.getText()); + sessionDownloadsStatsValue = new JLabel(sessionDownloadsStatsValue.getText()); + sessionUploadsStatsValue = new JLabel(sessionUploadsStatsValue.getText()); // set the new theme as the current one currentTheme = UIManager.getLookAndFeel().getName(); @@ -163,6 +170,8 @@ public Working(GuiSwing parent_) { JLabel user_info_credits_this_session = new JLabel("Points earned: ", JLabel.TRAILING); JLabel user_info_total_rendertime_this_session = new JLabel("Duration: ", JLabel.TRAILING); JLabel user_info_pending_uploads_and_size = new JLabel("Queued uploads: ", JLabel.TRAILING); + JLabel session_download_stats = new JLabel("Total Downloads: ", JLabel.TRAILING); + JLabel session_upload_stats = new JLabel("Total Uploads: ", JLabel.TRAILING); JLabel user_info_rendered_frame_this_session = new JLabel("Rendered frames: ", JLabel.TRAILING); JLabel global_static_renderable_project = new JLabel("Renderable projects: ", JLabel.TRAILING); @@ -175,6 +184,12 @@ public Working(GuiSwing parent_) { session_info_panel.add(user_info_pending_uploads_and_size); session_info_panel.add(userInfoQueuedUploadsAndSizeValue); + session_info_panel.add(session_download_stats); + session_info_panel.add(sessionDownloadsStatsValue); + + session_info_panel.add(session_upload_stats); + session_info_panel.add(sessionUploadsStatsValue); + session_info_panel.add(global_static_renderable_project); session_info_panel.add(renderable_projects_value); @@ -260,10 +275,10 @@ public Working(GuiSwing parent_) { widthLeftColumn = Spring.max(widthLeftColumn, getBestWidth(session_info_panel, 4, 2)); alignPanel(current_project_panel, 5, 2, widthLeftColumn); alignPanel(global_stats_panel, 4, 2, widthLeftColumn); - alignPanel(session_info_panel, 5, 2, widthLeftColumn); + alignPanel(session_info_panel, 7, 2, widthLeftColumn); // Set the proper size for the Working (if coming from Settings screen, the window size will be too big for the content!) - parent.setSize(520, 760); + parent.setSize(520, 820); } public void setStatus(String msg_) { @@ -303,6 +318,12 @@ public void setComputeMethod(String computeMethod_) { this.current_project_compute_method_value.setText(computeMethod_); } + public void displayTransferStats(TransferStats downloads, TransferStats uploads) { + sessionDownloadsStatsValue.setText(String.format("%s @ %s/s", downloads.getSessionTraffic(), downloads.getAverageSessionSpeed())); + sessionUploadsStatsValue.setText(String.format("%s @ %s/s", uploads.getSessionTraffic(), uploads.getAverageSessionSpeed())); + updateTime(); + } + public void displayStats(Stats stats) { DecimalFormat df = new DecimalFormat("##,##,##,##,##,##,##0"); remainingFrameContent.setText(df.format(stats.getRemainingFrame())); @@ -334,7 +355,7 @@ public void displayUploadQueueStats(int queueSize, long queueVolume) { } } - public void updateTime() { + public synchronized void updateTime() { if (this.parent.getClient().getStartTime() != 0) { user_info_total_rendertime_this_session_value .setText(Utils.humanDuration(new Date((new Date().getTime() - this.parent.getClient().getStartTime()))));