Skip to content

Commit

Permalink
Collect and log statistics for every disk cache garbage collection run.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 679111203
Change-Id: If8ecf6c0881e410506358725943bc8cc49d8533e
  • Loading branch information
tjgq authored and copybara-github committed Sep 26, 2024
1 parent ca802d6 commit 1690067
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicLong;

/**
* A garbage collector for the disk cache.
Expand Down Expand Up @@ -99,6 +100,12 @@ private long getTimeCutoff() {
}
}

private record DeletionStats(long deletedEntries, long deletedBytes) {}

/** Stats for a garbage collection run. */
public record CollectionStats(
long totalEntries, long totalBytes, long deletedEntries, long deletedBytes) {}

private final Path root;
private final CollectionPolicy policy;
private final ExecutorService executorService;
Expand Down Expand Up @@ -135,18 +142,24 @@ public CollectionPolicy getPolicy() {
* @throws IOException if an I/O error occurred
* @throws InterruptedException if the thread was interrupted
*/
public void run() throws IOException, InterruptedException {
public CollectionStats run() throws IOException, InterruptedException {
EntryScanner scanner = new EntryScanner();
EntryDeleter deleter = new EntryDeleter();

List<Entry> allEntries = scanner.scan();
List<Entry> entriesToDelete = policy.getEntriesToDelete(allEntries);

for (Entry entry : entriesToDelete) {
deleter.delete(root.getRelative(entry.path()));
deleter.delete(root.getRelative(entry.path()), entry.size());
}

deleter.await();
DeletionStats deletionStats = deleter.await();

return new CollectionStats(
allEntries.size(),
allEntries.stream().mapToLong(Entry::size).sum(),
deletionStats.deletedEntries(),
deletionStats.deletedBytes());
}

/** Lists all disk cache entries, performing I/O in parallel. */
Expand Down Expand Up @@ -203,6 +216,9 @@ private void visitDirectory(Path path) {

/** Deletes disk cache entries, performing I/O in parallel. */
private final class EntryDeleter extends AbstractQueueVisitor {
private final AtomicLong deletedEntries = new AtomicLong(0);
private final AtomicLong deletedBytes = new AtomicLong(0);

EntryDeleter() {
super(
executorService,
Expand All @@ -212,24 +228,28 @@ private final class EntryDeleter extends AbstractQueueVisitor {
}

/** Enqueues an entry to be deleted. */
void delete(Path path) {
void delete(Path path, long size) {
execute(
() -> {
try {
path.delete();
if (path.delete()) {
deletedEntries.incrementAndGet();
deletedBytes.addAndGet(size);
}
} catch (IOException e) {
throw new IORuntimeException(e);
}
});
}

/** Waits for all enqueued deletions to complete. */
void await() throws IOException, InterruptedException {
DeletionStats await() throws IOException, InterruptedException {
try {
awaitQuiescence(true);
} catch (IORuntimeException e) {
throw e.getCauseIOException();
}
return new DeletionStats(deletedEntries.get(), deletedBytes.get());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.flogger.GoogleLogger;
import com.google.devtools.build.lib.remote.disk.DiskCacheGarbageCollector.CollectionPolicy;
import com.google.devtools.build.lib.remote.disk.DiskCacheGarbageCollector.CollectionStats;
import com.google.devtools.build.lib.remote.options.RemoteOptions;
import com.google.devtools.build.lib.server.IdleTask;
import com.google.devtools.build.lib.vfs.Path;
Expand Down Expand Up @@ -84,12 +85,30 @@ public Duration delay() {
public void run() {
try {
logger.atInfo().log("Disk cache garbage collection started");
gc.run();
logger.atInfo().log("Disk cache garbage collection finished");
CollectionStats stats = gc.run();
logger.atInfo().log(
"Disk cache garbage collection finished: deleted %d of %d files, reclaimed %s of %s",
stats.deletedEntries(),
stats.totalEntries(),
formatBytes(stats.deletedBytes()),
formatBytes(stats.totalBytes()));
} catch (IOException e) {
logger.atInfo().withCause(e).log("Disk cache garbage collection failed");
} catch (InterruptedException e) {
logger.atInfo().withCause(e).log("Disk cache garbage collection interrupted");
}
}

private static String formatBytes(long bytes) {
if (bytes >= 1024 * 1024 * 1024) {
return "%3fGB".formatted((double) bytes / (1024 * 1024 * 1024));
}
if (bytes >= 1024 * 1024) {
return "%3fMB".formatted((double) bytes / (1024 * 1024));
}
if (bytes >= 1024) {
return "%3fKB".formatted((double) bytes / 1024);
}
return "%dB".formatted(bytes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
// limitations under the License.
package com.google.devtools.build.lib.remote.disk;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import com.google.common.util.concurrent.MoreExecutors;
import com.google.devtools.build.lib.remote.disk.DiskCacheGarbageCollector.CollectionStats;
import com.google.devtools.build.lib.testutil.TestUtils;
import com.google.devtools.build.lib.vfs.Path;
import java.io.IOException;
Expand Down Expand Up @@ -56,8 +58,9 @@ public void sizePolicy_noCollection() throws Exception {
Entry.of("ac/123", kbytes(1), Instant.now()),
Entry.of("cas/456", kbytes(1), Instant.now()));

runGarbageCollector(Optional.of(kbytes(2)), Optional.empty());
CollectionStats stats = runGarbageCollector(Optional.of(kbytes(2)), Optional.empty());

assertThat(stats).isEqualTo(new CollectionStats(2, kbytes(2), 0, 0));
assertFilesExist("ac/123", "cas/456");
}

Expand All @@ -69,8 +72,9 @@ public void sizePolicy_collectsOldest() throws Exception {
Entry.of("ac/abc", kbytes(1), daysAgo(3)),
Entry.of("cas/def", kbytes(1), daysAgo(4)));

runGarbageCollector(Optional.of(kbytes(2)), Optional.empty());
CollectionStats stats = runGarbageCollector(Optional.of(kbytes(2)), Optional.empty());

assertThat(stats).isEqualTo(new CollectionStats(4, kbytes(4), 2, kbytes(2)));
assertFilesExist("ac/123", "cas/456");
assertFilesDoNotExist("ac/abc", "cas/def");
}
Expand All @@ -81,8 +85,9 @@ public void agePolicy_noCollection() throws Exception {
Entry.of("ac/123", kbytes(1), Instant.now()),
Entry.of("cas/456", kbytes(1), Instant.now()));

runGarbageCollector(Optional.empty(), Optional.of(days(3)));
CollectionStats stats = runGarbageCollector(Optional.empty(), Optional.of(days(3)));

assertThat(stats).isEqualTo(new CollectionStats(2, kbytes(2), 0, 0));
assertFilesExist("ac/123", "cas/456");
}

Expand All @@ -94,8 +99,9 @@ public void agePolicy_collectsOldest() throws Exception {
Entry.of("ac/abc", kbytes(1), daysAgo(4)),
Entry.of("cas/def", kbytes(1), daysAgo(5)));

runGarbageCollector(Optional.empty(), Optional.of(Duration.ofDays(3)));
CollectionStats stats = runGarbageCollector(Optional.empty(), Optional.of(Duration.ofDays(3)));

assertThat(stats).isEqualTo(new CollectionStats(4, kbytes(4), 2, kbytes(2)));
assertFilesExist("ac/123", "cas/456");
assertFilesDoNotExist("ac/abc", "cas/def");
}
Expand All @@ -106,8 +112,9 @@ public void sizeAndAgePolicy_noCollection() throws Exception {
Entry.of("ac/123", kbytes(1), Instant.now()),
Entry.of("cas/456", kbytes(1), Instant.now()));

runGarbageCollector(Optional.of(kbytes(2)), Optional.of(days(1)));
CollectionStats stats = runGarbageCollector(Optional.of(kbytes(2)), Optional.of(days(1)));

assertThat(stats).isEqualTo(new CollectionStats(2, kbytes(2), 0, 0));
assertFilesExist("ac/123", "cas/456");
}

Expand All @@ -119,8 +126,9 @@ public void sizeAndAgePolicy_sizeMoreRestrictiveThanAge_collectsOldest() throws
Entry.of("ac/abc", kbytes(1), daysAgo(3)),
Entry.of("cas/def", kbytes(1), daysAgo(4)));

runGarbageCollector(Optional.of(kbytes(2)), Optional.of(days(4)));
CollectionStats stats = runGarbageCollector(Optional.of(kbytes(2)), Optional.of(days(4)));

assertThat(stats).isEqualTo(new CollectionStats(4, kbytes(4), 2, kbytes(2)));
assertFilesExist("ac/123", "cas/456");
assertFilesDoNotExist("ac/abc", "cas/def");
}
Expand All @@ -133,8 +141,9 @@ public void sizeAndAgePolicy_ageMoreRestrictiveThanSize_collectsOldest() throws
Entry.of("ac/abc", kbytes(1), daysAgo(3)),
Entry.of("cas/def", kbytes(1), daysAgo(4)));

runGarbageCollector(Optional.of(kbytes(3)), Optional.of(days(3)));
CollectionStats stats = runGarbageCollector(Optional.of(kbytes(3)), Optional.of(days(3)));

assertThat(stats).isEqualTo(new CollectionStats(4, kbytes(4), 2, kbytes(2)));
assertFilesExist("ac/123", "cas/456");
assertFilesDoNotExist("ac/abc", "cas/def");
}
Expand All @@ -144,8 +153,9 @@ public void ignoresTmpAndGcSubdirectories() throws Exception {
writeFiles(
Entry.of("gc/foo", kbytes(1), daysAgo(1)), Entry.of("tmp/foo", kbytes(1), daysAgo(1)));

runGarbageCollector(Optional.of(1L), Optional.of(days(1)));
CollectionStats stats = runGarbageCollector(Optional.of(1L), Optional.of(days(1)));

assertThat(stats).isEqualTo(new CollectionStats(0, 0, 0, 0));
assertFilesExist("gc/foo", "tmp/foo");
}

Expand All @@ -167,14 +177,14 @@ private void assertFilesDoNotExist(String... relativePaths) throws IOException {
}
}

private void runGarbageCollector(Optional<Long> maxSizeBytes, Optional<Duration> maxAge)
throws Exception {
private CollectionStats runGarbageCollector(
Optional<Long> maxSizeBytes, Optional<Duration> maxAge) throws Exception {
var gc =
new DiskCacheGarbageCollector(
rootDir,
executorService,
new DiskCacheGarbageCollector.CollectionPolicy(maxSizeBytes, maxAge));
gc.run();
return gc.run();
}

private void writeFiles(Entry... entries) throws IOException {
Expand Down

0 comments on commit 1690067

Please sign in to comment.