/* * Copyright (C) 2008-2011, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.lib; import java.time.Duration; import java.time.Instant; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.eclipse.jgit.lib.internal.WorkQueue; import org.eclipse.jgit.util.SystemReader; /** * ProgressMonitor that batches update events. */ public abstract class BatchingProgressMonitor implements ProgressMonitor { private static boolean performanceTrace = SystemReader.getInstance() .isPerformanceTraceEnabled(); private long delayStartTime; private TimeUnit delayStartUnit = TimeUnit.MILLISECONDS; private Task task; private Boolean showDuration; /** * Set an optional delay before the first output. * * @param time * how long to wait before output. If 0 output begins on the * first {@link #update(int)} call. * @param unit * time unit of {@code time}. */ public void setDelayStart(long time, TimeUnit unit) { delayStartTime = time; delayStartUnit = unit; } @Override public void start(int totalTasks) { // Ignore the number of tasks. } @Override public void beginTask(String title, int work) { endTask(); task = new Task(title, work); if (delayStartTime != 0) task.delay(delayStartTime, delayStartUnit); } @Override public void update(int completed) { if (task != null) task.update(this, completed); } @Override public void endTask() { if (task != null) { task.end(this); task = null; } } @Override public boolean isCancelled() { return false; } @Override public void showDuration(boolean enabled) { showDuration = Boolean.valueOf(enabled); } /** * Update the progress monitor if the total work isn't known, * * @param taskName * name of the task. * @param workCurr * number of units already completed. * @param duration * how long this task runs * @since 6.5 */ protected abstract void onUpdate(String taskName, int workCurr, Duration duration); /** * Finish the progress monitor when the total wasn't known in advance. * * @param taskName * name of the task. * @param workCurr * total number of units processed. * @param duration * how long this task runs * @since 6.5 */ protected abstract void onEndTask(String taskName, int workCurr, Duration duration); /** * Update the progress monitor when the total is known in advance. * * @param taskName * name of the task. * @param workCurr * number of units already completed. * @param workTotal * estimated number of units to process. * @param percentDone * {@code workCurr * 100 / workTotal}. * @param duration * how long this task runs * @since 6.5 */ protected abstract void onUpdate(String taskName, int workCurr, int workTotal, int percentDone, Duration duration); /** * Finish the progress monitor when the total is known in advance. * * @param taskName * name of the task. * @param workCurr * total number of units processed. * @param workTotal * estimated number of units to process. * @param percentDone * {@code workCurr * 100 / workTotal}. * @param duration * duration of the task * @since 6.5 */ protected abstract void onEndTask(String taskName, int workCurr, int workTotal, int percentDone, Duration duration); private boolean showDuration() { return showDuration != null ? showDuration.booleanValue() : performanceTrace; } /** * Append formatted duration if system property or environment variable * GIT_TRACE_PERFORMANCE is set to "true". If both are defined the system * property takes precedence. * * @param s * StringBuilder to append the formatted duration to * @param duration * duration to format * @since 6.5 */ @SuppressWarnings({ "boxing", "nls" }) protected void appendDuration(StringBuilder s, Duration duration) { if (!showDuration()) { return; } long hours = duration.toHours(); int minutes = duration.toMinutesPart(); int seconds = duration.toSecondsPart(); s.append(" ["); if (hours > 0) { s.append(hours).append(':'); s.append(String.format("%02d", minutes)).append(':'); s.append(String.format("%02d", seconds)); } else if (minutes > 0) { s.append(minutes).append(':'); s.append(String.format("%02d", seconds)); } else { s.append(seconds); } s.append('.').append(String.format("%03d", duration.toMillisPart())); if (hours > 0) { s.append('h'); } else if (minutes > 0) { s.append('m'); } else { s.append('s'); } s.append(']'); } private static class Task implements Runnable { /** Title of the current task. */ private final String taskName; /** Number of work units, or {@link ProgressMonitor#UNKNOWN}. */ private final int totalWork; /** True when timer expires and output should occur on next update. */ private volatile boolean display; /** Scheduled timer, supporting cancellation if task ends early. */ private Future timerFuture; /** True if the task has displayed anything. */ private boolean output; /** Number of work units already completed. */ private int lastWork; /** Percentage of {@link #totalWork} that is done. */ private int lastPercent; private final Instant startTime; Task(String taskName, int totalWork) { this.taskName = taskName; this.totalWork = totalWork; this.display = true; this.startTime = Instant.now(); } void delay(long time, TimeUnit unit) { display = false; timerFuture = WorkQueue.getExecutor().schedule(this, time, unit); } @Override public void run() { display = true; } void update(BatchingProgressMonitor pm, int completed) { lastWork += completed; if (totalWork == UNKNOWN) { // Only display once per second, as the alarm fires. if (display) { pm.onUpdate(taskName, lastWork, elapsedTime()); output = true; restartTimer(); } } else { // Display once per second or when 1% is done. int currPercent = Math.round(lastWork * 100F / totalWork); if (display) { pm.onUpdate(taskName, lastWork, totalWork, currPercent, elapsedTime()); output = true; restartTimer(); lastPercent = currPercent; } else if (currPercent != lastPercent) { pm.onUpdate(taskName, lastWork, totalWork, currPercent, elapsedTime()); output = true; lastPercent = currPercent; } } } private void restartTimer() { display = false; timerFuture = WorkQueue.getExecutor().schedule(this, 1, TimeUnit.SECONDS); } void end(BatchingProgressMonitor pm) { if (output) { if (totalWork == UNKNOWN) { pm.onEndTask(taskName, lastWork, elapsedTime()); } else { int currPercent = Math.round(lastWork * 100F / totalWork); pm.onEndTask(taskName, lastWork, totalWork, currPercent, elapsedTime()); } } if (timerFuture != null) timerFuture.cancel(false /* no interrupt */); } private Duration elapsedTime() { return Duration.between(startTime, Instant.now()); } } }