/* * Copyright 2015-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.HttpArtifactCacheEvent; import com.facebook.buck.event.NetworkEvent.BytesReceivedEvent; import com.facebook.buck.model.Pair; import com.facebook.buck.timing.Clock; import com.facebook.buck.timing.DefaultClock; import com.facebook.buck.util.unit.SizeUnit; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.time.Duration; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; public class NetworkStatsKeeper { private static final Duration DOWNLOAD_SPEED_CALCULATION_INTERVAL = Duration.ofMillis(1000); private final AtomicLong bytesDownloaded; private final AtomicLong bytesDownloadedInLastInterval; private final AtomicLong artifactDownloaded; private long artifactDownloadInProgressCount; private double downloadSpeedForLastInterval; private long firstDownloadStartTimestamp; private long lastDownloadFinishedTimeMs; private long totalDownloadTimeMillis; private long currentIntervalDownloadTimeMillis; private final ScheduledExecutorService scheduler; private Clock clock; public NetworkStatsKeeper() { this.bytesDownloaded = new AtomicLong(0); this.bytesDownloadedInLastInterval = new AtomicLong(0); this.artifactDownloaded = new AtomicLong(0); this.artifactDownloadInProgressCount = 0; this.downloadSpeedForLastInterval = 0; this.firstDownloadStartTimestamp = 0; this.lastDownloadFinishedTimeMs = 0; this.totalDownloadTimeMillis = 0; this.currentIntervalDownloadTimeMillis = 0; this.clock = new DefaultClock(); this.scheduler = Executors.newScheduledThreadPool( 1, new ThreadFactoryBuilder().setNameFormat(getClass().getSimpleName() + "-%d").build()); scheduleDownloadSpeedCalculation(); } private void scheduleDownloadSpeedCalculation() { long calculationInterval = DOWNLOAD_SPEED_CALCULATION_INTERVAL.toMillis(); TimeUnit timeUnit = TimeUnit.MILLISECONDS; @SuppressWarnings("unused") ScheduledFuture<?> unused = scheduler.scheduleAtFixedRate( this::calculateDownloadSpeedInLastInterval, /* initialDelay */ calculationInterval, /* period */ calculationInterval, timeUnit); } @VisibleForTesting void calculateDownloadSpeedInLastInterval() { synchronized (this) { long timeSpentDownloadingInThisInterval = getDownloadTimeForThisInterval(); downloadSpeedForLastInterval = calculateDownloadSpeed(timeSpentDownloadingInThisInterval); totalDownloadTimeMillis += timeSpentDownloadingInThisInterval; currentIntervalDownloadTimeMillis = 0; } } private double calculateDownloadSpeed(long timeSpentDownloadingInThisInterval) { if (timeSpentDownloadingInThisInterval <= 0) { return 0.0; } return ((double) 1000 * bytesDownloadedInLastInterval.getAndSet(0)) / timeSpentDownloadingInThisInterval; } private long getDownloadTimeForThisInterval() { long timeSpentDownloadingInThisInterval = currentIntervalDownloadTimeMillis; //Downloads may be interleaved. if (artifactDownloadInProgressCount != 0) { long currentTime = clock.currentTimeMillis(); timeSpentDownloadingInThisInterval += (currentTime - firstDownloadStartTimestamp); firstDownloadStartTimestamp = currentTime; } return timeSpentDownloadingInThisInterval; } public void bytesReceived(BytesReceivedEvent bytesReceivedEvent) { bytesDownloaded.getAndAdd(bytesReceivedEvent.getBytesReceived()); bytesDownloadedInLastInterval.getAndAdd(bytesReceivedEvent.getBytesReceived()); } public Pair<Long, SizeUnit> getBytesDownloaded() { return new Pair<>(bytesDownloaded.get(), SizeUnit.BYTES); } public Pair<Double, SizeUnit> getDownloadSpeed() { return new Pair<>(downloadSpeedForLastInterval, SizeUnit.BYTES); } public Pair<Double, SizeUnit> getAverageDownloadSpeed() { if (totalDownloadTimeMillis <= 0) { return new Pair<>(0.0, SizeUnit.BYTES); } double avgSpeed = ((double) 1000 * bytesDownloaded.get()) / totalDownloadTimeMillis; return new Pair<>(avgSpeed, SizeUnit.BYTES); } public void stopScheduler() { scheduler.shutdownNow(); } public long getDownloadedArtifactDownloaded() { return artifactDownloaded.get(); } public void artifactDownloadFinished(HttpArtifactCacheEvent.Finished event) { artifactDownloaded.incrementAndGet(); synchronized (this) { --artifactDownloadInProgressCount; if (event.getTimestamp() > lastDownloadFinishedTimeMs) { lastDownloadFinishedTimeMs = event.getTimestamp(); } // this is for calculating avg download speed accurately. Think the case where // download is interleaved. if (artifactDownloadInProgressCount == 0) { currentIntervalDownloadTimeMillis += (lastDownloadFinishedTimeMs - firstDownloadStartTimestamp); firstDownloadStartTimestamp = 0; } } } public void artifactDownloadedStarted(HttpArtifactCacheEvent.Started event) { synchronized (this) { ++artifactDownloadInProgressCount; if (firstDownloadStartTimestamp == 0 || event.getTimestamp() < firstDownloadStartTimestamp) { firstDownloadStartTimestamp = event.getTimestamp(); } } } //only for testing protected void setClock(Clock clock) { this.clock = clock; } }