/* * Copyright 2013-present Facebook, Inc. * * 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.facebook.buck.event.listener; import com.facebook.buck.artifact_cache.ArtifactCacheEvent; import com.facebook.buck.artifact_cache.HttpArtifactCacheEvent; import com.facebook.buck.distributed.DistBuildStatus; import com.facebook.buck.distributed.DistBuildStatusEvent; import com.facebook.buck.distributed.thrift.BuildSlaveStatus; import com.facebook.buck.event.ActionGraphEvent; import com.facebook.buck.event.BuckEvent; import com.facebook.buck.event.BuckEventBus; import com.facebook.buck.event.BuckEventListener; import com.facebook.buck.event.CommandEvent; import com.facebook.buck.event.ConsoleEvent; import com.facebook.buck.event.EventKey; import com.facebook.buck.event.InstallEvent; import com.facebook.buck.event.NetworkEvent; import com.facebook.buck.event.ProjectGenerationEvent; import com.facebook.buck.i18n.NumberFormatter; import com.facebook.buck.json.ParseBuckFileEvent; import com.facebook.buck.json.ProjectBuildFileParseEvents; import com.facebook.buck.log.Logger; import com.facebook.buck.model.BuildId; import com.facebook.buck.model.Pair; import com.facebook.buck.parser.ParseEvent; import com.facebook.buck.rules.BuildEvent; import com.facebook.buck.rules.BuildRuleEvent; import com.facebook.buck.rules.BuildRuleStatus; import com.facebook.buck.test.TestRuleEvent; import com.facebook.buck.timing.Clock; import com.facebook.buck.util.Ansi; import com.facebook.buck.util.Console; import com.facebook.buck.util.autosparse.AutoSparseStateEvents; import com.facebook.buck.util.environment.ExecutionEnvironment; import com.facebook.buck.util.unit.SizeUnit; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; import com.google.common.collect.RangeSet; import com.google.common.collect.TreeRangeSet; import com.google.common.eventbus.Subscribe; import java.io.Closeable; import java.io.IOException; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; /** * Base class for {@link BuckEventListener}s responsible for outputting information about the * running build to {@code stderr}. */ public abstract class AbstractConsoleEventBusListener implements BuckEventListener, Closeable { private static final Logger LOG = Logger.get(AbstractConsoleEventBusListener.class); private static final NumberFormatter TIME_FORMATTER = new NumberFormatter( locale1 -> { // Yes, this is the only way to apply and localize a pattern to a NumberFormat. NumberFormat numberFormat = NumberFormat.getIntegerInstance(locale1); Preconditions.checkState(numberFormat instanceof DecimalFormat); DecimalFormat decimalFormat = (DecimalFormat) numberFormat; decimalFormat.applyPattern("0.0s"); return decimalFormat; }); protected static final long UNFINISHED_EVENT_PAIR = -1; protected final Console console; protected final Clock clock; protected final Ansi ansi; private final Locale locale; protected ConcurrentHashMap<EventKey, EventPair> autoSparseState; @Nullable protected volatile ProjectBuildFileParseEvents.Started projectBuildFileParseStarted; @Nullable protected volatile ProjectBuildFileParseEvents.Finished projectBuildFileParseFinished; @Nullable protected volatile ProjectGenerationEvent.Started projectGenerationStarted; @Nullable protected volatile ProjectGenerationEvent.Finished projectGenerationFinished; protected ConcurrentLinkedDeque<ParseEvent.Started> parseStarted; protected ConcurrentLinkedDeque<ParseEvent.Finished> parseFinished; protected ConcurrentLinkedDeque<ActionGraphEvent.Started> actionGraphStarted; protected ConcurrentLinkedDeque<ActionGraphEvent.Finished> actionGraphFinished; protected ConcurrentHashMap<EventKey, EventPair> buckFilesProcessing; @Nullable protected volatile BuildEvent.Started buildStarted; @Nullable protected volatile BuildEvent.Finished buildFinished; @Nullable protected volatile BuildEvent.DistBuildStarted distBuildStarted; @Nullable protected volatile BuildEvent.DistBuildFinished distBuildFinished; @Nullable protected volatile InstallEvent.Started installStarted; @Nullable protected volatile InstallEvent.Finished installFinished; protected AtomicReference<HttpArtifactCacheEvent.Scheduled> firstHttpCacheUploadScheduled = new AtomicReference<>(); protected final AtomicInteger httpArtifactUploadsScheduledCount = new AtomicInteger(0); protected final AtomicInteger httpArtifactUploadsStartedCount = new AtomicInteger(0); protected final AtomicInteger httpArtifactUploadedCount = new AtomicInteger(0); protected final AtomicLong httpArtifactTotalBytesUploaded = new AtomicLong(0); protected final AtomicInteger httpArtifactUploadFailedCount = new AtomicInteger(0); @Nullable protected volatile HttpArtifactCacheEvent.Shutdown httpShutdownEvent; protected volatile Optional<Integer> ruleCount = Optional.empty(); protected Optional<String> publicAnnouncements = Optional.empty(); protected final AtomicInteger numRulesCompleted = new AtomicInteger(); protected Optional<ProgressEstimator> progressEstimator = Optional.empty(); protected final CacheRateStatsKeeper cacheRateStatsKeeper; protected final NetworkStatsKeeper networkStatsKeeper; private volatile Optional<Double> approximateDistBuildProgress = Optional.empty(); protected BuildRuleThreadTracker buildRuleThreadTracker; protected final Object distBuildStatusLock = new Object(); @GuardedBy("distBuildStatusLock") protected Optional<DistBuildStatus> distBuildStatus = Optional.empty(); public AbstractConsoleEventBusListener( Console console, Clock clock, Locale locale, ExecutionEnvironment executionEnvironment) { this.console = console; this.clock = clock; this.locale = locale; this.ansi = console.getAnsi(); this.projectBuildFileParseStarted = null; this.projectBuildFileParseFinished = null; this.projectGenerationStarted = null; this.projectGenerationFinished = null; this.parseStarted = new ConcurrentLinkedDeque<>(); this.parseFinished = new ConcurrentLinkedDeque<>(); this.actionGraphStarted = new ConcurrentLinkedDeque<>(); this.actionGraphFinished = new ConcurrentLinkedDeque<>(); this.buckFilesProcessing = new ConcurrentHashMap<>(); this.autoSparseState = new ConcurrentHashMap<>(); this.buildStarted = null; this.buildFinished = null; this.installStarted = null; this.installFinished = null; this.cacheRateStatsKeeper = new CacheRateStatsKeeper(); this.networkStatsKeeper = new NetworkStatsKeeper(); this.buildRuleThreadTracker = new BuildRuleThreadTracker(executionEnvironment); } @VisibleForTesting Optional<String> getPublicAnnouncements() { return publicAnnouncements; } public void setProgressEstimator(ProgressEstimator estimator) { progressEstimator = Optional.of(estimator); } protected String formatElapsedTime(long elapsedTimeMs) { long minutes = elapsedTimeMs / 60_000L; String seconds = TIME_FORMATTER.format(locale, elapsedTimeMs / 1000.0 - (minutes * 60)); return String.format(minutes == 0 ? "%s" : "%2$dm %1$s", seconds, minutes); } protected Optional<Double> getApproximateDistBuildProgress() { return approximateDistBuildProgress; } protected Optional<Double> getApproximateBuildProgress() { if (distBuildStarted != null && distBuildFinished == null) { return getApproximateDistBuildProgress(); } else { if (progressEstimator.isPresent()) { return progressEstimator.get().getApproximateBuildProgress(); } else { return Optional.empty(); } } } protected Optional<Double> getEstimatedProgressOfGeneratingProjectFiles() { if (progressEstimator.isPresent()) { return progressEstimator.get().getEstimatedProgressOfGeneratingProjectFiles(); } else { return Optional.empty(); } } protected Optional<Double> getEstimatedProgressOfProcessingBuckFiles() { if (progressEstimator.isPresent()) { return progressEstimator.get().getEstimatedProgressOfProcessingBuckFiles(); } else { return Optional.empty(); } } public void setPublicAnnouncements(BuckEventBus eventBus, Optional<String> announcements) { this.publicAnnouncements = announcements; if (announcements.isPresent()) { eventBus.post( ConsoleEvent.createForMessageWithAnsiEscapeCodes( Level.INFO, ansi.asInformationText(announcements.get()))); } } // This is used by the logging infrastructure to add a line to the console in a way that doesn't // break rendering. public abstract void printSevereWarningDirectly(String line); /** * Filter a list of events and return the subset that fall between the given start and end * timestamps. Preserves ordering if the given iterable was ordered. Will replace event pairs that * straddle the boundary with {@link com.facebook.buck.event.listener.ProxyBuckEvent} instances, * so that the resulting collection is strictly contained within the boundaries. * * @param start the start timestamp (inclusive) * @param end the end timestamp (also inclusive) * @param eventPairs the events to filter. * @return a list of all events from the given iterable that fall between the given start and end * times. If an event straddles the given start or end, it will be replaced with a proxy event * pair that cuts off at exactly the start or end. */ protected static Collection<EventPair> getEventsBetween( long start, long end, Iterable<EventPair> eventPairs) { List<EventPair> outEvents = new ArrayList<>(); for (EventPair ep : eventPairs) { long startTime = ep.getStartTime(); long endTime = ep.getEndTime(); if (ep.isComplete()) { if (startTime >= start && endTime <= end) { outEvents.add(ep); } else if (startTime >= start && startTime <= end) { // If the start time is within bounds, but the end time is not, replace with a proxy outEvents.add(EventPair.proxy(startTime, end)); } else if (endTime <= end && endTime >= start) { // If the end time is within bounds, but the start time is not, replace with a proxy outEvents.add(EventPair.proxy(start, endTime)); } } else if (ep.isOngoing()) { // If the event is ongoing, replace with a proxy outEvents.add(EventPair.proxy(startTime, end)); } // Ignore the case where we have an end event but not a start. Just drop that EventPair. } return outEvents; } /** * Adds a line about a pair of start and finished events to lines. * * @param prefix Prefix to print for this event pair. * @param suffix Suffix to print for this event pair. * @param currentMillis The current time in milliseconds. * @param offsetMs Offset to remove from calculated time. Set this to a non-zero value if the * event pair would contain another event. For example, build time includes parse time, but to * make the events easier to reason about it makes sense to pull parse time out of build time. * @param startEvent The started event. * @param finishedEvent The finished event. * @param lines The builder to append lines to. * @return The amount of time between start and finished if finished is present, otherwise {@link * AbstractConsoleEventBusListener#UNFINISHED_EVENT_PAIR}. */ protected long logEventPair( String prefix, Optional<String> suffix, long currentMillis, long offsetMs, @Nullable BuckEvent startEvent, @Nullable BuckEvent finishedEvent, Optional<Double> progress, ImmutableList.Builder<String> lines) { if (startEvent == null) { return UNFINISHED_EVENT_PAIR; } EventPair pair = EventPair.builder() .setStart(startEvent) .setFinish(Optional.ofNullable(finishedEvent)) .build(); return logEventPair( prefix, suffix, currentMillis, offsetMs, ImmutableList.of(pair), progress, lines); } /** * Adds a line about a the state of cache uploads to lines. * * @param lines The builder to append lines to. */ protected void logHttpCacheUploads(ImmutableList.Builder<String> lines) { if (firstHttpCacheUploadScheduled.get() != null) { boolean isFinished = httpShutdownEvent != null; lines.add( String.format( "[%s] HTTP CACHE UPLOAD...%s%s", isFinished ? "-" : "+", isFinished ? "FINISHED " : "", renderHttpUploads())); } } /** * Adds a line about a set of start and finished events to lines. * * @param prefix Prefix to print for this event pair. * @param suffix Suffix to print for this event pair. * @param currentMillis The current time in milliseconds. * @param offsetMs Offset to remove from calculated time. Set this to a non-zero value if the * event pair would contain another event. For example, build time includes parse time, but to * make the events easier to reason about it makes sense to pull parse time out of build time. * @param eventPairs the collection of start/end events to sum up when calculating elapsed time. * @param lines The builder to append lines to. * @return The summed time between start and finished events if each start event has a matching * finished event, otherwise {@link AbstractConsoleEventBusListener#UNFINISHED_EVENT_PAIR}. */ protected long logEventPair( String prefix, Optional<String> suffix, long currentMillis, long offsetMs, Collection<EventPair> eventPairs, Optional<Double> progress, ImmutableList.Builder<String> lines) { if (eventPairs.isEmpty()) { return UNFINISHED_EVENT_PAIR; } long completedRunTimesMs = getTotalCompletedTimeFromEventPairs(eventPairs); long currentlyRunningTime = getWorkingTimeFromLastStartUntilNow(eventPairs, currentMillis); boolean stillRunning = currentlyRunningTime >= 0; String parseLine = (stillRunning ? "[+] " : "[-] ") + prefix + "..."; long elapsedTimeMs = completedRunTimesMs - offsetMs; if (stillRunning) { elapsedTimeMs += currentlyRunningTime; } else { parseLine += "FINISHED "; if (progress.isPresent()) { progress = Optional.of(1.0); } } parseLine += formatElapsedTime(elapsedTimeMs); if (progress.isPresent()) { parseLine += " [" + Math.round(progress.get() * 100) + "%]"; } if (suffix.isPresent()) { parseLine += " " + suffix.get(); } lines.add(parseLine); return stillRunning ? UNFINISHED_EVENT_PAIR : elapsedTimeMs; } /** * Takes a collection of start and finished events. If there are any events that have a start, but * no finished time, the collection is considered ongoing. * * @param eventPairs the collection of event starts/stops. * @param currentMillis the current time. * @return -1 if all events are completed, otherwise the time elapsed between the latest event and * currentMillis. */ protected static long getWorkingTimeFromLastStartUntilNow( Collection<EventPair> eventPairs, long currentMillis) { // We examine all events to determine whether we have any incomplete events and also // to get the latest timestamp available (start or stop). long latestTimestamp = 0L; long earliestOngoingStart = Long.MAX_VALUE; boolean anyEventIsOngoing = false; for (EventPair pair : eventPairs) { if (pair.isOngoing()) { anyEventIsOngoing = true; if (pair.getStartTime() < earliestOngoingStart) { latestTimestamp = pair.getStartTime(); } } else if (pair.getEndTime() > latestTimestamp) { latestTimestamp = pair.getEndTime(); } } // If any events are unpaired, the whole collection is considered ongoing and we return // the difference between the latest time in the collection and the current time. return anyEventIsOngoing ? currentMillis - latestTimestamp : -1; } /** * Get the summed elapsed time from all matched event pairs. Does not consider unmatched event * pairs. Pairs are determined by their {@link com.facebook.buck.event.EventKey}. * * @param eventPairs a set of paired events (incomplete events are okay). * @return the sum of all times between matched event pairs. */ protected static long getTotalCompletedTimeFromEventPairs(Collection<EventPair> eventPairs) { long totalTime = 0L; // Flatten the event groupings into a timeline, so that we don't over count parallel work. RangeSet<Long> timeline = TreeRangeSet.create(); for (EventPair pair : eventPairs) { if (pair.isComplete() && pair.getElapsedTimeMs() > 0) { timeline.add(Range.open(pair.getStartTime(), pair.getEndTime())); } } for (Range<Long> range : timeline.asRanges()) { totalTime += range.upperEndpoint() - range.lowerEndpoint(); } return totalTime; } /** Formats a {@link ConsoleEvent} and adds it to {@code lines}. */ protected void formatConsoleEvent(ConsoleEvent logEvent, ImmutableList.Builder<String> lines) { if (logEvent.getMessage() == null) { LOG.error("Got logEvent with null message"); return; } String formattedLine = ""; if (logEvent.containsAnsiEscapeCodes() || logEvent.getLevel().equals(Level.INFO)) { formattedLine = logEvent.getMessage(); } else if (logEvent.getLevel().equals(Level.WARNING)) { formattedLine = ansi.asWarningText(logEvent.getMessage()); } else if (logEvent.getLevel().equals(Level.SEVERE)) { formattedLine = ansi.asHighlightedFailureText(logEvent.getMessage()); } if (!formattedLine.isEmpty()) { // Split log messages at newlines and add each line individually to keep the line count // consistent. lines.addAll(Splitter.on("\n").split(formattedLine)); } } @Subscribe public void commandStartedEvent(CommandEvent.Started startedEvent) { if (progressEstimator.isPresent()) { progressEstimator .get() .setCurrentCommand(startedEvent.getCommandName(), startedEvent.getArgs()); } } public static void aggregateStartedEvent( ConcurrentHashMap<EventKey, EventPair> map, BuckEvent started) { map.compute( started.getEventKey(), (key, pair) -> pair == null ? EventPair.builder().setStart(started).build() : pair.withStart(started)); } public static void aggregateFinishedEvent( ConcurrentHashMap<EventKey, EventPair> map, BuckEvent finished) { map.compute( finished.getEventKey(), (key, pair) -> pair == null ? EventPair.builder().setFinish(finished).build() : pair.withFinish(finished)); } @Subscribe public void autoSparseStateSparseRefreshStarted( AutoSparseStateEvents.SparseRefreshStarted started) { aggregateStartedEvent(autoSparseState, started); } @Subscribe public void autoSparseStateSparseRefreshFinished( AutoSparseStateEvents.SparseRefreshFinished finished) { aggregateFinishedEvent(autoSparseState, finished); } @Subscribe public void projectBuildFileParseStarted(ProjectBuildFileParseEvents.Started started) { projectBuildFileParseStarted = started; } @Subscribe public void projectBuildFileParseFinished(ProjectBuildFileParseEvents.Finished finished) { projectBuildFileParseFinished = finished; } @Subscribe public void projectGenerationStarted(ProjectGenerationEvent.Started started) { projectGenerationStarted = started; } @SuppressWarnings("unused") @Subscribe public void projectGenerationProcessedTarget(ProjectGenerationEvent.Processed processed) { if (progressEstimator.isPresent()) { progressEstimator.get().didGenerateProjectForTarget(); } } @Subscribe public void projectGenerationFinished(ProjectGenerationEvent.Finished finished) { projectGenerationFinished = finished; if (progressEstimator.isPresent()) { progressEstimator.get().didFinishProjectGeneration(); } } @Subscribe public void parseStarted(ParseEvent.Started started) { parseStarted.add(started); aggregateStartedEvent(buckFilesProcessing, started); } @Subscribe public void ruleParseFinished(ParseBuckFileEvent.Finished ruleParseFinished) { if (progressEstimator.isPresent()) { progressEstimator.get().didParseBuckRules(ruleParseFinished.getNumRules()); } } @Subscribe public void parseFinished(ParseEvent.Finished finished) { parseFinished.add(finished); if (progressEstimator.isPresent()) { progressEstimator.get().didFinishParsing(); } aggregateFinishedEvent(buckFilesProcessing, finished); } @Subscribe public void actionGraphStarted(ActionGraphEvent.Started started) { actionGraphStarted.add(started); aggregateStartedEvent(buckFilesProcessing, started); } @Subscribe public void actionGraphFinished(ActionGraphEvent.Finished finished) { actionGraphFinished.add(finished); aggregateFinishedEvent(buckFilesProcessing, finished); } @Subscribe public void buildStarted(BuildEvent.Started started) { buildStarted = started; if (progressEstimator.isPresent()) { progressEstimator.get().didStartBuild(); } } @Subscribe public void distBuildStarted(BuildEvent.DistBuildStarted started) { distBuildStarted = started; } @Subscribe public void ruleCountCalculated(BuildEvent.RuleCountCalculated calculated) { ruleCount = Optional.of(calculated.getNumRules()); if (progressEstimator.isPresent()) { progressEstimator.get().setNumberOfRules(calculated.getNumRules()); } cacheRateStatsKeeper.ruleCountCalculated(calculated); } @Subscribe public void ruleCountUpdated(BuildEvent.UnskippedRuleCountUpdated updated) { ruleCount = Optional.of(updated.getNumRules()); if (progressEstimator.isPresent()) { progressEstimator.get().setNumberOfRules(ruleCount.get()); } cacheRateStatsKeeper.ruleCountUpdated(updated); } protected Optional<String> getOptionalBuildLineSuffix() { // Log build time, excluding time spent in parsing. String jobSummary = null; if (ruleCount.isPresent()) { List<String> columns = new ArrayList<>(); columns.add(String.format(locale, "%d/%d JOBS", numRulesCompleted.get(), ruleCount.get())); CacheRateStatsKeeper.CacheRateStatsUpdateEvent cacheRateStats = cacheRateStatsKeeper.getStats(); columns.add(String.format(locale, "%d UPDATED", cacheRateStats.getUpdatedRulesCount())); if (ruleCount.orElse(0) > 0) { columns.add( String.format( locale, "%d [%.1f%%] CACHE MISS", cacheRateStats.getCacheMissCount(), cacheRateStats.getCacheMissRate())); if (cacheRateStats.getCacheErrorCount() > 0) { columns.add( String.format( locale, "%d [%.1f%%] CACHE ERRORS", cacheRateStats.getCacheErrorCount(), cacheRateStats.getCacheErrorRate())); } } jobSummary = "(" + Joiner.on(", ").join(columns) + ")"; } return Strings.isNullOrEmpty(jobSummary) ? Optional.empty() : Optional.of(jobSummary); } protected String getNetworkStatsLine(@Nullable BuildEvent.Finished finishedEvent) { String parseLine = (finishedEvent != null ? "[-] " : "[+] ") + "DOWNLOADING" + "..."; List<String> columns = new ArrayList<>(); if (finishedEvent != null) { Pair<Double, SizeUnit> avgDownloadSpeed = networkStatsKeeper.getAverageDownloadSpeed(); Pair<Double, SizeUnit> readableSpeed = SizeUnit.getHumanReadableSize(avgDownloadSpeed.getFirst(), avgDownloadSpeed.getSecond()); columns.add( String.format( locale, "%s/S " + "AVG", SizeUnit.toHumanReadableString(readableSpeed, locale))); } else { Pair<Double, SizeUnit> downloadSpeed = networkStatsKeeper.getDownloadSpeed(); Pair<Double, SizeUnit> readableDownloadSpeed = SizeUnit.getHumanReadableSize(downloadSpeed.getFirst(), downloadSpeed.getSecond()); columns.add( String.format( locale, "%s/S", SizeUnit.toHumanReadableString(readableDownloadSpeed, locale))); } Pair<Long, SizeUnit> bytesDownloaded = networkStatsKeeper.getBytesDownloaded(); Pair<Double, SizeUnit> readableBytesDownloaded = SizeUnit.getHumanReadableSize(bytesDownloaded.getFirst(), bytesDownloaded.getSecond()); columns.add( String.format( locale, "TOTAL: %s", SizeUnit.toHumanReadableString(readableBytesDownloaded, locale))); columns.add( String.format( locale, "%d Artifacts", networkStatsKeeper.getDownloadedArtifactDownloaded())); return parseLine + " " + "(" + Joiner.on(", ").join(columns) + ")"; } @Subscribe public void buildRuleStarted(BuildRuleEvent.Started started) { if (progressEstimator.isPresent()) { progressEstimator.get().didStartRule(); } buildRuleThreadTracker.didStartBuildRule(started); } @Subscribe public void buildRuleResumed(BuildRuleEvent.Resumed resumed) { if (progressEstimator.isPresent()) { progressEstimator.get().didResumeRule(); } buildRuleThreadTracker.didResumeBuildRule(resumed); } @Subscribe public void buildRuleSuspended(BuildRuleEvent.Suspended suspended) { if (progressEstimator.isPresent()) { progressEstimator.get().didSuspendRule(); } buildRuleThreadTracker.didSuspendBuildRule(suspended); } @Subscribe public void buildRuleFinished(BuildRuleEvent.Finished finished) { if (finished.getStatus() != BuildRuleStatus.CANCELED) { if (progressEstimator.isPresent()) { progressEstimator.get().didFinishRule(); } numRulesCompleted.getAndIncrement(); } buildRuleThreadTracker.didFinishBuildRule(finished); cacheRateStatsKeeper.buildRuleFinished(finished); } @Subscribe public void distBuildFinished(BuildEvent.DistBuildFinished finished) { distBuildFinished = finished; } @Subscribe public void buildFinished(BuildEvent.Finished finished) { buildFinished = finished; if (progressEstimator.isPresent()) { progressEstimator.get().didFinishBuild(); } } @Subscribe public void testRuleStarted(TestRuleEvent.Started started) { buildRuleThreadTracker.didStartTestRule(started); } @Subscribe public void testRuleFinished(TestRuleEvent.Finished finished) { buildRuleThreadTracker.didFinishTestRule(finished); } @Subscribe public void installStarted(InstallEvent.Started started) { installStarted = started; } @Subscribe public void installFinished(InstallEvent.Finished finished) { installFinished = finished; } @Subscribe public void onHttpArtifactCacheScheduledEvent(HttpArtifactCacheEvent.Scheduled event) { if (event.getOperation() == ArtifactCacheEvent.Operation.STORE) { firstHttpCacheUploadScheduled.compareAndSet(null, event); httpArtifactUploadsScheduledCount.incrementAndGet(); } } @Subscribe public void onHttpArtifactCacheStartedEvent(HttpArtifactCacheEvent.Started event) { if (event.getOperation() == ArtifactCacheEvent.Operation.STORE) { httpArtifactUploadsStartedCount.incrementAndGet(); } else { networkStatsKeeper.artifactDownloadedStarted(event); } } @Subscribe public void onHttpArtifactCacheFinishedEvent(HttpArtifactCacheEvent.Finished event) { if (event.getOperation() == ArtifactCacheEvent.Operation.STORE) { if (event.getStoreData().wasStoreSuccessful().orElse(false)) { httpArtifactUploadedCount.incrementAndGet(); Optional<Long> artifactSizeBytes = event.getStoreData().getArtifactSizeBytes(); if (artifactSizeBytes.isPresent()) { httpArtifactTotalBytesUploaded.addAndGet(artifactSizeBytes.get()); } } else { httpArtifactUploadFailedCount.incrementAndGet(); } } else { networkStatsKeeper.artifactDownloadFinished(event); } } @Subscribe public void onHttpArtifactCacheShutdownEvent(HttpArtifactCacheEvent.Shutdown event) { httpShutdownEvent = event; } @Subscribe public void onDistBuildStatusEvent(DistBuildStatusEvent event) { int totalRuleCount = 0; int finishedRuleCount = 0; synchronized (distBuildStatusLock) { distBuildStatus = Optional.of(event.getStatus()); } for (BuildSlaveStatus status : event.getStatus().getSlaveStatuses()) { totalRuleCount += status.getTotalRulesCount(); finishedRuleCount += status.getRulesFinishedCount(); } if (totalRuleCount != 0) { double buildProgress = (double) finishedRuleCount / totalRuleCount; approximateDistBuildProgress = Optional.of(Math.floor(100 * buildProgress) / 100.0); } else { approximateDistBuildProgress = Optional.empty(); } } @Subscribe public void bytesReceived(NetworkEvent.BytesReceivedEvent bytesReceivedEvent) { networkStatsKeeper.bytesReceived(bytesReceivedEvent); } protected String renderHttpUploads() { long bytesUploaded = httpArtifactTotalBytesUploaded.longValue(); String humanReadableBytesUploaded = SizeUnit.toHumanReadableString( SizeUnit.getHumanReadableSize(bytesUploaded, SizeUnit.BYTES), locale); int scheduled = httpArtifactUploadsScheduledCount.get(); int complete = httpArtifactUploadedCount.get(); int failed = httpArtifactUploadFailedCount.get(); int uploading = httpArtifactUploadsStartedCount.get() - (complete + failed); int pending = scheduled - (uploading + complete + failed); if (scheduled > 0) { return String.format( "%s (%d COMPLETE/%d FAILED/%d UPLOADING/%d PENDING)", humanReadableBytesUploaded, complete, failed, uploading, pending); } else { return humanReadableBytesUploaded; } } @Override public void outputTrace(BuildId buildId) {} @Override public void close() throws IOException { networkStatsKeeper.stopScheduler(); } }