Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ build.properties
hs_err_pid*.log
.DS_Store

# deb packaging
# compiled class files
*.class

jdiskmark-deb

# rpm packaging
Expand Down
34 changes: 28 additions & 6 deletions src/jdiskmark/BenchmarkRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ public Benchmark execute() throws Exception {
int[][] tRanges = divideIntoRanges(startingSample, endingSample, config.numThreads);

benchmark.recordStartTime();

// #134 Trigger GC before benchmark to reduce GC interference during measurements
GcManager.triggerPreBenchmarkGc();

// Execution Loops
if (config.hasWriteOperation()) {
Expand Down Expand Up @@ -163,12 +166,31 @@ private void runOperation(Benchmark b, IOMode mode, int[][] ranges) throws Excep
for (int s = range[0]; s < range[1] && !listener.isCancelled(); s++) {
Sample.Type type = mode == IOMode.WRITE ? Sample.Type.WRITE : Sample.Type.READ;
Sample sample = new Sample(type, s);
try {
action.perform(sample);
} catch (Exception ex) {
logger.log(Level.SEVERE, null, ex);
throw new RuntimeException(ex);
}

// #134 Detect GC during measurement and retake sample if needed
int attempts = 0;
boolean gcDetected;
do {
if (attempts > 0) {
// Undo progress increments from the GC-affected attempt before retrying
switch (mode) {
case WRITE -> writeUnitsComplete.add(-config.numBlocks);
case READ -> readUnitsComplete.add(-config.numBlocks);
}
logger.warning("GC detected during sample " + s
+ ", retaking measurement (retry " + attempts
+ " of " + GcManager.MAX_GC_RETRIES + ")");
}
long gcCountBefore = GcManager.getTotalGcCount();
try {
action.perform(sample);
} catch (Exception ex) {
logger.log(Level.SEVERE, null, ex);
throw new RuntimeException(ex);
}
gcDetected = GcManager.gcOccurredSince(gcCountBefore);
attempts++;
} while (gcDetected && attempts <= GcManager.MAX_GC_RETRIES);

//TODO: review for putting into onSampleComplete
App.updateMetrics(sample);
Expand Down
64 changes: 64 additions & 0 deletions src/jdiskmark/GcManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package jdiskmark;

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.logging.Logger;

/**
* Utility class for JVM garbage collection management to minimize the effects
* of GC events on benchmark sample measurements. Addresses issue #134.
*
* <p>Strategies implemented:
* <ol>
* <li>Trigger a GC cycle before each benchmark to reduce the likelihood of
* a GC event occurring during sample measurements.</li>
* <li>Detect GC events during a sample measurement and retake the sample
* if GC is detected, up to {@link #MAX_GC_RETRIES} times.</li>
* </ol>
*/
public class GcManager {

private static final Logger logger = Logger.getLogger(GcManager.class.getName());

/** Maximum number of times a sample will be retaken due to a GC event. */
static final int MAX_GC_RETRIES = 3;

private GcManager() {}

/**
* Triggers a GC cycle before a benchmark starts to reduce the likelihood
* of a GC event occurring during sample measurements (issue #134, recommendation #3).
*/
public static void triggerPreBenchmarkGc() {
logger.fine("Triggering pre-benchmark GC to minimize GC interference during benchmark");
System.gc();
}

/**
* Returns the total number of GC collections across all GC collectors.
* Uses the standard {@link GarbageCollectorMXBean} API.
*
* @return total GC collection count, or 0 if unavailable
*/
public static long getTotalGcCount() {
long total = 0;
for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
long count = bean.getCollectionCount();
if (count > 0) {
total += count;
}
}
return total;
}

/**
* Returns {@code true} if at least one GC collection has occurred since
* the given snapshot count was taken.
*
* @param gcCountSnapshot the GC count taken before the operation to check
* @return true if GC occurred since the snapshot
*/
public static boolean gcOccurredSince(long gcCountSnapshot) {
return getTotalGcCount() > gcCountSnapshot;
}
}